公開日:2022.07.20

FlutterでWebSocketをさわってみた

テクログ

はじめに

お久しぶりです!
のりさん です

最近 WebSocket (リアルタイム通信) に興味を持ったので
今回はFlutterでWebSocketを使った簡易アプリを作ってみたいと思います。

WebSocketということで今回はバックエンド側もDockerで構築していきます。

環境

  • 開発PC:Windows10
  • フロントエンド:Flutter 3.0.2 (Web)
  • バックエンド:NestJS 9.0(Node.js) ※Docker

アプリ内容

FlutterカウンターアプリのWebSocket版

バックエンド側

まずはDockerでNestJS(Node.js)を使用するためのファイルを準備していきます

①作業用のフォルダを作成し以下の2ファイルを保存します
(自分の場合 VSCodeでフォルダを開いて作業しました)

FROM node:18-buster-slim

RUN \
apt update && apt -y install rsync procps && \
npm i -g @nestjs/cli && \
mkdir /var/tmp/projects && \
cd /var/tmp/projects && \
# nestjsのプロジェクトを作成
nest new --skip-git --package-manager npm websocket && \
cd websocket && \
# WebSocket関連のパッケージをインストール
npm i --save @nestjs/websockets @nestjs/platform-socket.io && \
npm i --save-dev @types/socket.io && \
# WebSocket用にgatewayを新しく作成
nest g gateway websocket/events && \
mkdir /projects

# コンテナ起動時のスクリプト作成
RUN \
{ \
  echo '#!/bin/bash'; \
  # ホスト側にソースがなければコピーする
  echo 'if [ ! -d "/projects/websocket/src" ]; then'; \
  echo '  rsync -ahv --no-i-r  --exclude "/var/tmp/projects/websocket/node_modules" --info=progress2 /var/tmp/projects/ /projects/'; \
  echo 'fi'; \
  # ホスト側にnode_modulesがなければコピーする
  echo 'if [ -d "/host_node_modules" ] && [ ! -f "/host_node_modules/.package-lock.json" ]; then'; \
  echo '  cp -rTv /projects/websocket/node_modules /host_node_modules'; \
  echo 'fi'; \
  echo 'cd /projects/websocket'; \
  # 開発モードでnestjsを起動
  echo 'npm run start:dev'; \
} > /usr/bin/entrypoint.sh && \ 
chmod +x /usr/bin/entrypoint.sh

WORKDIR /projects
ENTRYPOINT [ "entrypoint.sh" ]

さくっと環境構築できるようにDockerfileに起動スクリプトを埋め込んでみました
初回起動時にホスト側へNestJSのプロジェクトがコピーされます

version: "3.8"
services:
  nestjs:
    image: nestjs-app
    container_name: nestjs-app-container
    build: .
    ports:
      - "3000:3000"
    volumes:
      - "./projects/websocket/node_modules:/host_node_modules:delegated"
      - "./projects:/projects"
      - "/projects/websocket/node_modules"

②ではさっそくターミナルでdockerコンテナを起動してみましょう

先ほど作った作業フォルダに移動(cd)して以下を実行します
(※初回だけNestJSプロジェクトのコピーに時間がかかります)

 > docker-compose up --build

projectsにwebsocket用のプロジェクトが作成されていればOKです
既にNestJSが開発モードで起動しているかと思います
(開発モードは typescriptファイルを監視していてファイルを修正すると自動で再起動してくれます)

③WebSocketのサーバー側を実装します
src/websocket/events.gateway.ts を編集していきます

import { SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway()
export class EventsGateway {
  @WebSocketServer()
  server: Server;
	
  // カウンター
  counter: number = 0; 

  // カウンターのインクリメント
  @SubscribeMessage('countUp')
  countUp(client: any, payload: any): number {
	  this.counter++;
	  // 各クライアントへカウンター更新を通知(changeCounter実行)
	  this.server.emit("changeCounter", this.counter);
    return this.counter;
  }

  // 現在のカウンターを取得
  @SubscribeMessage('getCounter')
  getCounter(client: any, payload: any): number {
    return this.counter;
  }

}

tsファイルを保存するとサーバーが再起動されるので確認がしやすいです。
バックエンド側はこれで終わりです。

フロントエンド側

続いてFlutter側を実装していきます。

①Android StudioやVSCode等から新しいFlutterプロジェクトを作成します。
(カウンターサンプルアプリが作成された状態にします)

②カウンターアプリをWebSocket対応版に修正

以下の2ファイルを修正していきます

dependencies:
  flutter:
    sdk: flutter
<省略>
  socket_io_client: ^2.0.0

pubspec.yamlに「socket_io_client: ^2.0.0」を追加してpub getしておきます

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:socket_io_client/socket_io_client.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late final Socket _socket;

  @override
  void initState() {

    final String url;
    if( !kIsWeb && Platform.isAndroid) {
      // AndroidシュミレータはホストOSにアクセス
      url = "http://10.0.2.2:3000";
    } else {
      // それ以外はlocalhostにアクセス
      url = "http://localhost:3000";
    }

    _socket = io(
        url,
        OptionBuilder().setTransports(['websocket']).disableAutoConnect().build()
    );

    _socket.onConnect((data) {
      print("WebSocket Connected");
      // 現在のカウンターを取得
      _socket.emitWithAck("getCounter", null, ack: (counter) {
        setState(() => _counter = counter);
      });
    });

    _socket.onDisconnect((_) => print("WebSocket Disconnected"));

    _socket.on('changeCounter', (counter) {
      // カウンターが変更された場合
      setState(() => _counter = counter);
    });

    // WebSocketに接続
    _socket.connect();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text('$_counter', style: Theme.of(context).textTheme.headline4),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ()=>_socket.emit("countUp"),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

emitメソッドを使ってサーバー側と通信しています。
これでフロント側も終わりました。

それでは出来たアプリを実行してみます!

完成アプリ

Firefox、Edge、Androidアプリ で確認してみます。
(ブラウザ以外も確認したいと思ったのでchromeではなくAndroidアプリにしました)

それぞれWebSocketの接続がうまくいっているようです。
+ボタン押下時にWebSocket経由でカウンターがインクリメントされ
更新されたことが他のクライアントにもリアルタイムで伝わっているようですね。

おわりに

今回はFlutterとNestJSを使ってWebSocketの簡単なアプリを作ってみました。
これを使えばオンライン対戦ゲームやチャット系のツール等が作れそうですね!

それではまた!

この記事を書いた人

のりさん

入社年2014年

出身地東京都

業務内容開発

特技または趣味水泳・筋トレ・旅行

のりさんの記事一覧へ

テクログに関する記事一覧