Build a chat application in Dart (Part 3)
Tags: dart, http, beginners, showdev
Published: 2019-04-10 10:09:21

In [Part 2](https://creativebracket.com/build-a-chat-application-in-dart-3/) we refactored our solution by splitting the Sign in and Chat room logic into `View` classes, building a `Router` object to transition between the screens. We will complete the full flow here by implementing our WebSocket connection and managing each of the chatters in our Chat room. At the end of this tutorial we will have a functioning chat application.

---

## 1. Create a WebSocket object

In order for messages to be relayed from one user and broadcasted to the others, we need to establish a persistent, bi-directional connection to our server. This will allow messages to be sent back and forth between the server and client using an event-based architecture. This is made possible via the WebSocket protocol, which the **dart:io** and **dart:html** library provides classes for. _So no external packages needed!_

Let's begin on the client by writing a class to be responsible for instantiating a `WebSocket` object and implementing various event listeners on them. This class will represent a user's connection to our Chat room server.

### Build a Chat room subject

Create a `chat_room_subject.dart` file inside the directory `lib/src` and implement the class for our `ChatRoomSubject`:

```dart
// lib/src/chat_room_subject.dart

import 'dart:html';

class ChatRoomSubject {
  ChatRoomSubject(String username) {}
  
  final WebSocket socket;
}
```
Because we've marked our `socket` instance variable as **final**, it requires that we assign a value to it in the initialize phase, that is, before the `{}` part of our constructor is run. Let's therefore create our `WebSocket` instance and pass the URL to our server:

```dart
ChatRoomSubject(String username)
  : socket = WebSocket('ws://localhost:9780/ws?username=$username') {}
```
When our `WebSocket` class is instantiated, a connection will be established to the URL passed in as it's `String` argument. We've also passed in the username value as part of the query string of the `WebSocket` URL.

We are now able to listen for several events on our `WebSocket` namely **open**, **message**, **error** and **close**.

```dart
// ..
class ChatRoomSubject {
  // ..
  // ..
  _initListeners() {
    socket.onOpen.listen((evt) {
      print('Socket is open');
    });

    // Please note: "message" event will be implemented elsewhere

    socket.onError.listen((evt) {
      print('Problems with socket. ${evt}');
    });

    socket.onClose.listen((evt) {
      print('Socket is closed');
    });
  }
}
```
And we need to invoke the `_initListeners()` method:

```dart
ChatRoomSubject(String username)
  : socket = WebSocket('ws://localhost:9780/ws?username=$username') {
  _initListeners(); // <-- Added this line
}
```
We need to be able to send messages to our Chat room server and close a WebSocket connection, effectively leaving the Chat room. Add these methods to our `ChatRoomSubject` class before `_initListeners()`:

```dart
send(String data) => socket.send(data);
close() => socket.close();
```

### Integrate `ChatRoomSubject` into the `ChatRoomView` class

Import the dart file we've just created from web/views/chat_room.dart directly after our `// Absolute imports` (`dart_bulma_chat_app` is a reference to the name value field inside your pubspec.yaml file, which points to the `lib/` directory):

```dart
// web/views/chat_room.dart

// Absolute imports
import 'dart:html';
import 'dart:convert';

// Package imports
import 'package:dart_bulma_chat_app/src/chat_room_subject.dart'; // <-- Added this line
```
And then we will instantiate this class as follows:

```dart
// ..
// ..
class ChatRoomView implements View {
  ChatRoomView(this.params)
      : _contents = DocumentFragment(),
        _subject = ChatRoomSubject(params['username']) // <-- Added this line
  {
    onEnter();
  }

  /// Properties
  final ChatRoomSubject _subject;
  // ..
  // ..
}
```
If you look at the `_initListeners()` method in `ChatRoomSubject` you should notice that we didn't implement the listener for the WebSocket "**message**" event. We will do that in `ChatRoomView` since we have access to the `WebSocket` instance:

```dart
// ..
// ..
class ChatRoomView implements View {
  // ..
  // ..
  void _addEventListeners() {
    // ..
    // ..
    _subject.socket.onMessage.listen(_subjectMessageHandler);
  }
  
  void _subjectMessageHandler(evt) {
    chatRoomLog.appendHtml(evt.data + '<br />');
  }
}
```
Lastly, let's amend `_sendBtnClickHandler(e)` to send messages from the input field to the Chat server, empty the message input field value and reset it's focus:

```dart
void _sendBtnClickHandler(e) {
  _subject.send(messageField.value);

  // Resets value and re-focuses on input field
  messageField
    ..value = ''
    ..focus();
}
```

## 2. Upgrade incoming requests to WebSocket connections

To get a basic example working, let's write some server-side logic to maintain our chat room session.

Create `lib/src/chat_room_session.dart` and implement the class for our `ChatRoomSession`:

```dart
import 'dart:io';
import 'dart:convert';

class Chatter {
  Chatter({this.session, this.socket, this.name});
  HttpSession session;
  WebSocket socket;
  String name;
}

class ChatRoomSession {
  final List<Chatter> _chatters = [];

  // TODO: Implement addChatter method
  // TODO: Implement removeChatter method
  // TODO: Implement notifyChatters method
}
```
Here we have two different classes. The `Chatter` class represents each participant. This participant contains its current session, WebSocket connection and name. The `ChatRoomSession` class manages our list of participants, including facilitating communication between them.

Here's the implementation of the `addChatter()` method. This upgrades the incoming request to a WebSocket connection, builds a `Chatter` object, creates event listeners and adds it to the `_chatters` list:

```dart
class ChatRoomSession {
  final List<Chatter> _chatters = [];

  addChatter(HttpRequest request, String username) async {
    // Upgrade request to `WebSocket` connection and add to `Chatter` object
    WebSocket ws = await WebSocketTransformer.upgrade(request);
    Chatter chatter = Chatter(
      session: request.session,
      socket: ws,
      name: username,
    );

    // Listen for incoming messages, handle errors and close events
    chatter.socket.listen(
      (data) => _handleMessage(chatter, data),
      onError: (err) => print('Error with socket ${err.message}'),
      onDone: () => _removeChatter(chatter),
    );

    _chatters.add(chatter);

    print('[ADDED CHATTER]: ${chatter.name}');
  }
  
  // TODO: Implement _handleMessage method
  _handleMessage(Chatter chatter, String data) {}
}
```
When messages are sent to the server via the connection, we handle this by invoking `_handleMessage()` while passing it the `Chatter` object and `data` as arguments. 

Let's cross out that `// TODO:` by implementing `_handleMessage()`:

```dart
_handleMessage(Chatter chatter, String data) {
  chatter.socket.add('You said: $data');
  _notifyChatters(chatter, data);
}
```
And let's implement `_notifyChatters()`:

```dart
_notifyChatters(Chatter exclude, [String message]) {
  _chatters
    .where((chatter) => chatter.name != exclude.name)
    .toList()
    .forEach((chatter) => chatter.socket.add(message));
}
```

And also removing the `Chatter` object when a participant leaves:

```dart
_removeChatter(Chatter chatter) {
  print('[REMOVING CHATTER]: ${chatter.name}');
  _chatters.removeWhere((c) => c.name == chatter.name);
  _notifyChatters(chatter, '${chatter.name} has left the chat.');
}
```

### Instantiate our `ChatRoomSession` class

Go to `bin/server.dart` and create our instance:

```dart
import 'dart:io';
import 'dart:convert';

import 'package:dart_bulma_chat_app/src/chat_room_session.dart'; // <-- Added this line

main() async {
  // TODO: Get port from environment variable
  var port = 9780;
  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
  var chatRoomSession = ChatRoomSession(); // <-- Added this line
  // ..
  // ..
}
```
Scroll down to the `case '/ws':` section and call the `addChatter()` method, passing it the request and username:

```dart
// ..
// .. 
  case '/ws':
    String username = request.uri.queryParameters['username'];
    chatRoomSession.addChatter(request, username);
    break;
// ..
// ..
```
So we retrieve the **username** from the query parameters when we passed `?username=$username` into the URL we defined for our `WebSocket` instance inside `ChatRoomSubject`.

Let us test what we have now. Open a terminal session and instantiate the server:

```bash
dart bin/server.dart
```

And run the web app on another terminal session:

```bash
webdev serve --live-reload
```

Following this fully should give you the image result below:

![Chat app working demo](https://thepracticaldev.s3.amazonaws.com/i/g3ospivocfhlo0w1p36v.gif)

If you’ve got this far then great job!!!

## 3. Implement some helper functions

We need a way of categorising the types of messages sent to our Chat server and respond to it accordingly. When a participant joins the chat, it would be great to send a notification welcoming this new participant and notifying the other participants on the new chatter. We will create an enum type which will contain the particular types of messages this Chat app will have. 

Follow the full tutorial which includes tidying up the look and feel of the chat messages and implementing the logout functionality. 

→ [**Read the full tutorial on my blog**](https://creativebracket.com/build-a-chat-application-in-dart-3/)

---

## Sharing is caring 🤗

If you enjoyed reading this post, please share this through the various social channels. I'm looking for some Patrons to keep these detailed articles coming. [**Become a Patron**](https://patreon.com/creativebracket) today...it will really make my day. Alternatively, you could [**buy me a coffee**](https://ko-fi.com/creativebracket) instead.

Watch my [Free **Get started with Dart** course](https://egghead.io/courses/get-started-with-dart) on Egghead.io and [**Subscribe to my email newsletter**](http://eepurl.com/gipQBX) to download my Free 35-page **Get started with Dart** eBook and to be notified when new content is released.

**Like, share and [follow me](https://twitter.com/creativ_bracket)** 😍 for more content on Dart.