2022.07.20
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
はじめに
お久しぶりです!
のりさん です
最近 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の簡単なアプリを作ってみました。
これを使えばオンライン対戦ゲームやチャット系のツール等が作れそうですね!
それではまた!