Build a chat application in Dart 2 (Part 2)
Tags: dart, http, beginners, showdev
Published: 2018-11-08 11:41:02

In [Part 1](https://dev.to/graphicbeacon/build-a-basic-chat-application-in-dart-2-part-1-4ekj) we constructed the UI for our Chat sign in screen and wrote a basic flow to transition to the Chatroom UI once the username is successfully validated.

In this part, we will refactor our working solution and implement a basic router to handle transitioning between views. Here's the diagram again of the chat flow:

![Diagram of chat flow](https://thepracticaldev.s3.amazonaws.com/i/l1s6n4fkcpur4hy7xwe2.png)

We have a bit to cover, so without further ado lets begin!

---

## 1. Encapsulate logic for sign in UI

Currently we have our application logic inside `web/main.dart`, which will be a maintenance nightware should we continue to build on that logic! So we are going to manage the logic for each screen in a `View` class.

Create the directory below inside the `web` folder with the listed files:

```bash
views/
  chat_room.dart
  chat_signin.dart
  view.dart
```
Now in `view.dart`, create an interface which will be used as a blueprint for our View classes:

```dart
abstract class View {
  void onEnter(); // Run this when the view is loaded in
  void onExit();  // Run this when we exit the view
  void prepare(); // Prepare the view template, register event handlers etc... before mounting on the page
  void render();  // Render the view on the screen
}
```
_At this point some of you may be wondering why we are using `abstract class` instead of the `interface` keyword? **Answer:** The Dart team made it so! The rationale is that classes are implicit interfaces so to simplify things they stuck with abstract classes. This means that we can either **extend** or **implement** an abstract class._

Let's use this interface to implement our view inside `chat_signin.dart`:

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

// Relative imports
import './view.dart';

class ChatSigninView implements View {
  ChatSigninView() : _contents = DocumentFragment() {
    onEnter();
  }

  /// Properties
  DocumentFragment _contents;
  DivElement chatSigninBox;
  ParagraphElement validationBox;
  InputElement nameField;
  ButtonElement submitBtn;
  HttpRequest _response;

  @override
  void onEnter() {
    prepare();
    render();
  }

  @override
  void onExit() {}

  @override
  void prepare() {}

  @override
  void render() {}
}
```
Before the constructor body is run, we initiate `_contents` with a new `DocumentFragment`. We will populate it with our template and define our event handlers before inserting into the page.

Let's implement the `prepare()` method to use `_contents`:

```dart
@override
void prepare() {
  _contents.innerHtml = '''
  <div id="ChatSignin">
      <h1 class="title">Chatter 🎯</h1>
      <div class="columns">
        <div class="column is-6">
          <div class="field">
            <label class="label">Please enter your name</label>
            <div class="control is-expanded has-icons-left">
              <input class="input is-medium" type="text" placeholder="Enter your name and hit ENTER" />
              <span class="icon is-medium is-left">
                <i class="fas fa-user"></i>
              </span>
            </div>
            <p class="help is-danger"></p>
          </div>
          <div class="field">
            <div class="control">
              <button class="button is-medium is-primary">
                Join chat
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
    ''';

  chatSigninBox = _contents.querySelector('#ChatSignin');
  validationBox = chatSigninBox.querySelector('p.help');
  nameField = chatSigninBox.querySelector('input[type="text"]');
  submitBtn = chatSigninBox.querySelector('button');

  _addEventListeners(); // TODO: Implement this method
}
```
And now to implement `_addEventListeners()` and its related methods:

```dart
class ChatSigninView implements View {
  ...
  ...
  void _addEventListeners() {
    // Event listeners on form controls
    nameField.addEventListener('input', _inputHandler);
    submitBtn.addEventListener('click', _clickHandler);
  }

  void _inputHandler(evt) {
    if (nameField.value.trim().isNotEmpty) {
      nameField.classes
        ..removeWhere((className) => className == 'is-danger')
        ..add('is-success');
      validationBox.text = '';
    } else {
      nameField.classes
        ..removeWhere((className) => className == 'is-success')
        ..add('is-danger');
    }
  }

  void _clickHandler(evt) async {
    // Validate name field
    if (nameField.value.trim().isEmpty) {
      nameField.classes.add('is-danger');
      validationBox.text = 'Please enter your name';
      return;
    }

    submitBtn.disabled = true;

    // Submit name to backend via POST
    try {
      _response = await HttpRequest.postFormData(
        'http://localhost:9780/signin',
        {
          'username': nameField.value,
        },
      );

      // Handle success response and switch view
      onExit();
    } catch (e) {
      // Handle failure response
      submitBtn
        ..disabled = false
        ..text = 'Failed to join chat. Try again?';
    }
  }
}
```
Most of the logic above has been moved from `web/main.dart` from Part 1 of the series.

The `_inputHandler()` private method is responsible for validating the input text field and adding the appropriate classes. The `_clickHandler()` private method will validate and submit the form, POSTing to the endpoint at `http://localhost:9780/signin`. The result is assigned to the `_response` instance variable. We then call the `onExit()` method, which will pass the response data to the next view.

Let's render the prepared document fragment to the screen:

```dart
@override
void render() {
  querySelector('#app')
    ..innerHtml = ''
    ..append(_contents);
}
```
And define our exit strategy:

```dart
@override
void onExit() {
  nameField.removeEventListener('input', _inputHandler);
  submitBtn.removeEventListener('click', _clickHandler);

  // Swap view to chat room
  // TODO: Transition to Chat room screen
}
```
To see this view in the browser, update `web/main.dart`:

```dart
import './views/chat_signin.dart';

void main() {
  ChatSigninView();
}
```
Change the `<body>` markup of `web/index.html` as follows:

```html
<section class="section">
  <div class="container" id="app">
    <!-- Views will be rendered here -->
  </div>
</section>
```

Run the `webdev` server and visit `http://localhost:8080` in the browser:

```bash
webdev serve --live-reload
```
![Chat signin view](https://thepracticaldev.s3.amazonaws.com/i/vgzc4i81sv7hkibzgk8c.png)

## 2. Encapsulate logic for Chat room UI

Define a `ChatRoomView` class inside `web/views/chat_room.dart`:

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

// Relative imports
import './view.dart';

class ChatRoomView implements View {
  ChatRoomView(this.params)
      : _contents = DocumentFragment() {
    onEnter();
  }

  /// Properties
  Map params;
  DocumentFragment _contents;
  DivElement chatRoomBox;
  DivElement chatRoomLog;
  InputElement messageField;
  ButtonElement sendBtn;

  @override
  void onEnter() {
    prepare();
    render();
  }

  @override
  void onExit() {
    _removeEventListeners(); // TODO: Implement this method
    
    // TODO: Transition to chat sign in screen
  }

  @override
  void prepare() {
  _contents.innerHtml = '''
    <div id="ChatRoom">
        <h1 class="title">Chatroom</h1>
        <div class="tile is-ancestor">
          <div class="tile is-8 is-vertical is-parent">
            <div class="tile is-child box">
              <div id="ChatRoomLog"></div>
            </div>
            <div class="tile is-child">
              <div class="field has-addons">
                <div class="control is-expanded has-icons-left">
                  <input id="ChatRoomMessageInput" class="input is-medium" type="text" placeholder="Enter message" />
                  <span class="icon is-medium is-left">
                    <i class="fas fa-keyboard"></i>
                  </span>
                </div>
                <div class="control">
                  <button id="ChatRoomSendBtn" class="button is-medium is-primary">
                    Send&nbsp;&nbsp;
                    <span class="icon is-medium">
                      <i class="fas fa-paper-plane"></i>
                    </span>
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      ''';

    chatRoomBox = _contents.querySelector('#ChatRoom');
    chatRoomLog = chatRoomBox.querySelector('#ChatRoomLog');
    messageField = chatRoomBox.querySelector('#ChatRoomMessageInput');
    sendBtn = chatRoomBox.querySelector('#ChatRoomSendBtn');

    _addEventListeners(); // TODO: Implement this method next
  }

  @override
  void render() {
    querySelector('#app')
      ..innerHtml = ''
      ..append(_contents);
  }
}
```
And now to implement `_addEventListeners()` and `_removeEventListeners()`:

```dart
void _addEventListeners() {
  sendBtn.disabled = true;

  /// Event listeners
  messageField.addEventListener('input', _messageFieldInputHandler);
  sendBtn.addEventListener('click', _sendBtnClickHandler);
}

void _removeEventListeners() {
  messageField.removeEventListener('input', _messageFieldInputHandler);
  sendBtn.removeEventListener('click', _sendBtnClickHandler);
}

void _messageFieldInputHandler(e) {
  // Disable the send button if message input is empty
  sendBtn.disabled = messageField.value.isEmpty;
}

void _sendBtnClickHandler(e) {
  // TODO: Broadcast message to other chat users
  messageField.value = '';
}
```
Now that the logic for both views are encapsulated, we need to be able to transition between the two. Now you can do this easily by replacing the `TODO: Transition to * screen` with instantiating the class for the view you want to launch, so `ChatRoomView()` or `ChatSigninView()`.

I'm not too fond of that approach since it means importing one class into the other and vice versa. I would rather delegate this to a separate class to handle that concern. 

That brings us to Step #3 below...

## 3. Implement a `Router` class for handling view transition

In `web/router.dart` define a `Router` class:

```dart
import './views/view.dart';

// Type definition to label a Function that returns a `View` type
typedef ViewInstantiateFn = View Function(Map data);

class Router {
  Router() : _routes = [];

  List<Map<String, ViewInstantiateFn>> _routes;

  register(String path, ViewInstantiateFn viewInstance) {
    // The key `path` is a computed property
    // It could also be written as {'$path': viewInstance}
    _routes.add({path: viewInstance});
  }

  go(String path, {Map params = null}) {
    // Find the matching `Map` object in _routes
    // and invoke it's `View` object instance
    _routes.firstWhere(
      (Map<String, ViewInstantiateFn> route) => route.containsKey(path),
      orElse: () => null,
    )[path](params ?? {});
  }
}

Router router = Router();
```
The `Router` class contains a list of routes under the `_routes` instance variable. Each route is a `Map` containing a key name which is the path and the value of that key is a function that returns a `View` object. We've created a type definition called `ViewInstantiateFn` to represent the structure of that function.

To add a route we will call the `register()` method, passing it a path and a function to instantiate our `View`. To transition to the view we will call the `go()` method, passing it a path and a map of parameters to be consumed by the `View`.

The last line of the file exports a `Router` instance.

Let's use this class in `web/main.dart`:

```dart
import './router.dart';
import './views/chat_room.dart';
import './views/chat_signin.dart';

void main() {
  router
    ..register('/', (_) => ChatSigninView())
    ..register('/chat-room', (params) => ChatRoomView(params))
    ..go('/');
}
```
Go to `web/views/chat_signin.dart` and replace `// TODO: Transition to Chat room screen` with a call to `router.go()`:

```dart
import '../router.dart'; // <-- Remember to import this
..
..

  @override
  void onExit() {
    nameField.removeEventListener('input', _inputHandler);
    submitBtn.removeEventListener('click', _clickHandler);

    // Swap view to chat room
    router.go('/chat-room', params: {'username': _response.responseText}); // <-- Added this line
  }
```
Go to `web/views/chat_room.dart` and replace `// TODO: Transition to chat sign in screen` in `onExit()` method:

```dart
import '../router.dart'; // <-- Remember to import this
..
..

  @override
  void onExit() {
    _removeEventListeners();

    router.go('/'); // <-- Added this line
  }
```

Now run the backend server in a separate terminal:

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

Here's what we should now have:

![Working solution](https://thepracticaldev.s3.amazonaws.com/i/89vqkr7i0647hc5rmyer.gif)

Add this css rule in `web/styles.css` to chat room message log bigger:

```css
#ChatRoomLog {
  height: 300px;
  overflow-y: scroll;
}
```

## Conclusion

And this concludes Part 2 of the series. In Part 3, we will implement the chat conversation logic and complete this series. 

As always, I hope this was insightful and you learnt something new today. And the working solution is [here](https://github.com/graphicbeacon/dart-bulma-chat-app).

**Like, share and follow me** 😍 for more content on Dart.

## Further reading

1. [dart:html library](https://api.dartlang.org/stable/2.0.0/dart-html/dart-html-library.html)
2. [How to use JavaScript libraries in your Dart applications](https://dev.to/graphicbeacon/how-to-use-javascript-libraries-in-your-dart-applications--4mc6)
3. [**Full-Stack Web Development with Dart**](http://bit.ly/2P2N1jC)