FlutterでWebSocketのチャットアプリを作ってみる

# やりたいこと
Flutterを利用してiOS/Android/Desktopで動くWebSocketのチャット機能を作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282054/83467c49-1557-e72e-1b71-4757878eed5b.png)


### なぜやるのか
1. Flutterの学習
2. WebSocketでリアルタイム通信を利用した次のアプリを作成する足掛かり

# 利用するパッケージ
[web_socket_channel](https://pub.dev/packages/web_socket_channel)

# 事前準備
1. Flutterのインストール https://flutter.dev/docs/get-started/install
2. flutter doctorコマンド正常終了
3. flutter createコマンドでプロジェクト作成

# 実装
変更箇所だけ、先に紹介します。

```yaml:pubspec.yaml
dependencies:
  # add start---------------------
  io: ^0.3.4
  html: ^0.14.0+4
  web_socket_channel: ^1.2.0
  # add end  ---------------------
  flutter:
    sdk: flutter
```

```xml:macos/Runner/DebugProfile.entitlements
	<key>com.apple.security.network.client</key>
	<true/>
```

```xml:macos/Runner/Release.entitlements
	<key>com.apple.security.network.client</key>
	<true/>
```

```dart:lib/main.dart
import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chat app',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChatPage(),
    );
  }
}

class ChatPage extends StatefulWidget {
  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  List<String> messages = [];
  WebSocketChannel channel;

  @override
  void initState() {
    super.initState();
    // channel = IOWebSocketChannel.connect(Uri.parse('wss://echo.websocket.org'));
    channel = IOWebSocketChannel.connect(Uri.parse('ws://localhost:8081'));
    channel.stream.listen((message) {
      setState(() {
        messages.add(message);
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
    channel.sink.close(status.goingAway);
  }

  @override
  Widget build(BuildContext context) {
    TextEditingController _controller = TextEditingController();
    void _sendMessage() {
      if (_controller.text.isNotEmpty) {
        channel.sink.add(_controller.text);
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: Text('WebSocket chat app'),
      ),
      body: ListView(
        children: [
          ...messages.map((element) => ListTile(title: Text(element)))
        ],
      ),
      bottomNavigationBar: BottomAppBar(
        shape: const CircularNotchedRectangle(),
        child: Container(
          height: 100.0,
          child: Form(
            child: TextFormField(
              decoration: const InputDecoration(
                icon: Icon(Icons.message),
                labelText: 'Send message',
              ),
              textInputAction: TextInputAction.next,
              controller: _controller,
              validator: (String value) {
                return null;
              },
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendMessage,
        tooltip: 'Send message',
        child: Icon(Icons.send),
      ),
    );
  }
}

```


## 解説

### WebSocket接続

```dart
    // channel = IOWebSocketChannel.connect(Uri.parse('wss://echo.websocket.org'));
    channel = IOWebSocketChannel.connect(Uri.parse('ws://localhost:8081'));
```
WebSocketサーバに接続して、チャンネルを取得します。

### メッセージ取得

```dart
    channel.stream.listen((message) {
      setState(() {
        messages.add(message);
      });
    });
  }
```

受信したメッセージをStateに格納して、アプリに表示します。

### メッセージ送信

```dart
    void _sendMessage() {
      if (_controller.text.isNotEmpty) {
        channel.sink.add(_controller.text);
      }
    }
```
入力されたメッセージを送信します。



### WebSocket切断

```dart
    channel.sink.close(status.goingAway);
```
チャンネルを切断します。

# やってみて感想
Flutterのドキュメントや参考サイトが少なく手が出せていませんでしたが、
今回アプリを開発してみようと調べてみると、公式ドキュメントや技術ページが想像以上に充実していてすぐ実装できました。

お恥ずかしながら、一番時間がかかったのは`flutter doctor`をクリアすることでした。

次は、WebSocketをシグナリングサーバとして、WebRTCにチャレンジしてみます。


# リポジトリ公開しました

https://github.com/Yamamoto-Shohei/sample_flutter_websocket