FC2ブログ

スマフォのアプリを作りたい(12):React Nativeでチャットアプリ味見⑤

   プログラミング [2020/02/07]
いつ、このサブタイトル「React Nativeでチャットアプリ味見」から離れられるか、ちょっと想像付かなくなってきてます。
ちょいちょいっと簡単なものなら作れるかと思って始めましたが、そうも行ってません。
深いです・・・っていうか、やたら色んなモノが関係してきて、分けがわかんなくなりそうです。
調べを続けて行く内に、前のコトの意味を忘れていきます。年ですな。

「スマフォのアプリを作りたい」で始めたこの話の状況はこんなんなってます。
Start
 →スマフォのアプリを作りたい
  →スマフォ側の環境はReact Nativeを使用します
   →試しにチャットアプリでReact Nativeコードを味見して見ます
    →スマフォ側のUIをサンプル・コードで動かしてみました
     (react-native-gifted-chatを使ったサンプル)
    →サーバ側動作環境を準備した
     (RatChetを使ったサンプル)
    →スマフォとサーバでチャット動作を試したい
←今回はココ

と、ここで・・・「前のスマフォ側コードにチャットサーバを設定しているようなところを見かけた気がしない。Sendとかやってたっけ?」「react-native-gifted-chat」はUI-ONLYなのかしら?
まずは、そこから調べはじめます。

「スマフォのアプリを作りたい(8)・・・」辺りから読んでいないと、以下の内容は読んでも理解いただけないかと思います。特に前提となるチャットサーバ環境/準備手順等については、前回記事をご参照ください。


◆Gifred-Chatは通信処理を含むのか?
以下を読むと、どうも含んでいないらしい。
参考:https://qiita.com/Satoooon/items/b3d781f1caafe394f134

でもって、「react native websocket」で検索すると・・・
React NativeでWebsocketを使うフレームワークがあるらしい。
参考:https://github.com/tiaanduplessis/react-native-websocket
でも、その中身と使用例を見ると、なんだか違和感がある。
WSというコンポーネントが定義されていて、それを使うようになっているみたいだが、
GiftedChatコンポーネントの中のメソッドからどうやってそれらを使うのかちょっとイメージできない。

さらに、検索して以下のページが出てくる。
React Native本家:https://facebook.github.io/react-native/docs/network
上記の「WebSocket Support」の部分の説明で概要は分かった気になれたが、react-native-websocketの利用例とはまったく別ものです。
はい。ここで勘違いしてはいけないのが、本家の説明はWebSocketの基本的な使い方であって、react-native-websocketのことではないということです。

さらに、「React Native Cookbook 著者:Stan Bershadskiy, Crysfel Villa」という市販本の一部をGoogleブックスから見ることができました。P213辺りでWebSocketの使い方が説明されています。URLはクソ長かったので省略します。

前に試したChat.jsに、react-native-websocketではなく、素のWebSocketを使うように処理を追加すればできる気になってきました。
やってみます。


以下は、無駄になったreact-native-websocketのインストール方法です。読み飛ばしてください。




◆react-native-websocketのインストール(不要です)
以前、React Nativeのテンプレート環境にreact-native-gifted-chatをインストールしたときのやり方でいいのかと思います。
コマンドプロンプト(管理者権限)を起動して、以下の如く。
----コマンドプロンプト-----------------------------
C:\WINDOWS\system32>cd /d D:\AppMake\proj\VTChat

D:\AppMake\proj\VTChat>npm install react-native-websocket --save
npm WARN @typescript-eslint/eslint-plugin@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN @typescript-eslint/parser@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react@7.12.4 requires a peer of eslint@^3.0.0 || ^4.0.0 || ^5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-react-native@3.6.0 requires a peer of eslint@^3.17.0 || ^4 || ^5 but none is installed. You must install peer dependencies yourself.
npm WARN tsutils@3.17.1 requires a peer of typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.11 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ react-native-websocket@1.0.2
added 1 package from 1 contributor and audited 952806 packages in 81.028s
found 4 low severity vulnerabilities
run `npm audit fix` to fix them, or `npm audit` for details
---------------------------------------------------

できたっぽいです。
「(プロジェクトフォルダ\node_modules」の下に「react-native-websocket」というフォルダが作成されています。





スマフォ・アプリ側にWebSocket通信の処理を実装します。・・・


◆Chat.jsの変更
サーバアドレスとポート番号は、後で変更可能なように.jsonから持ってきたいなーと思って、chatserver.jsonを作ります。
----chatserver.json--------------------------------
{
"name": "localhost",
"port": "8080"
}
---------------------------------------------------

Char.js側は、以下のように変更した。(面倒なので全体を載せちゃいます。)
----Chat.js----------------------------------------
/**
* Chat Sample
*
* @format
* @flow
*/

import React, {Component} from 'react';
import {
View,
Text,
} from 'react-native';
import {
GiftedChat
} from 'react-native-gifted-chat';
import {
name as srvName,
port as srvPort
} from './chatserver.json';



export default class Chat extends Component {

//メッセージ内容をstateで管理
state = {
messages:[],
};

//WebSocketコンポーネント生成
componentWillMount() {
chatsrvUrl = 'ws://' + srvName + ':' + srvPort;
this.ws = new WebSocket(chatsrvUrl);
//WebSocketのイベントハンドラの指定
this.ws.onopen = this.onOpenConnection;
this.ws.onmessage = this.onMessageReceived;
this.ws.onerror = this.onError;
this.ws.onclose = this.onClose;
}

//WebSocket:接続イベント
onOpenConnection = () => {
console.log('Open!');
}
//WebSocket:エラーいべんと
onError = (event) => {
console.log('Error!', event.message);
}
//WebSocket:切断イベント
onClose = (event) => {
console.log('Close!', event.code, event.reason);
}
//WebSocket:メッセージ受信処理
onMessageReceived = (event) => {
console.log('Receive!', event.data);
}


//「Send」ボタンが押された時に実行されるメソッド
onSend = (messages = []) => {
this.ws.send(messages);
this.setState((previousState) => ({
//stateで管理しているmessagesに送信されたメッセージを追加
messages: GiftedChat.append(previousState.messages, messages),
}));
}

render() {
return (
//react-native-gifted-chatが提供するコンポーネント
messages={this.state.messages}//stateで管理しているメッセージ
onSend={(messages) => this.onSend(messages)}//送信ボタン押した時の動作
user={{
_id: 1,
}}
//locale='ja' //日本語対応
/>
)
}
}
---------------------------------------------------
変更内容は、全部「追加」です。
newするWebSocketクラス・コンポーネントは、「(プロジェクトフォルダ)\node_modules\react-native\Libraries\WebSocket\WebSocket.js」に含まれていました。

ここで、componentWillmount()というメソッドが登場しています。
React Nativeの入門サイトをローラーしていたときに、一瞬この辺を分かり易く説明していたサイトを見かけたのですが、よく読まずに流してしまいました。改めてお勉強が必要でした。

コンポーネントの「ライフサイクル」という言い方で説明があります。
ライフサイクルについては(一部Reactの引用だけど)以下。
参考:https://fresopiya.com/2019/04/03/life-cycle/

つまり、React Nativeがコンポーネント処理を進めているときに呼ばれるコールバック関数ですね。
コンポーネント中に定義しておけば、そのコンポーネントが実行?されるときに自動的に呼ばれるということのようです。



-ちょっと脱線:ちなみにws:って?-
サーバアドレスの指定に「ws:」と使っていますが、WebSocketプロトコルのスキーマ?を表すもので、デフォルトのポート番号が80になるそうです。また、「wss:」と指定した場合は、WebSocketのセキュアチャンネルを指定していて、デフォルトのポート番号は443だそうです。←どっかの参考サイトの説明の丸写しで何を言っているのか意味はさっぱり分かってません。


以降、図の表示が小さくて見えない場合は、クリックすると文字が見える程度の画像で表示すると思います。


◆チャットアプリの実行とデバッグ
まずは、チャットサーバ・プログラムとApacheを起動します。詳細は前回記事をご参照ください。

ともかく実行(react-native run-android)させてみます。
詳細はこの記事辺りからご参照ください。

結果は、
Error! Failed to connect to localhost/127.0.0.1:8080」とデバッガ(Chromeのデベロッパーツール:Console)上に出てます。new(connection)に失敗しているようです。
何度リロードさせても同じ。

アドレスかポートの指定が受け入れられてない?

1)チャットサーバーのテストで使ったHTMLをエミュレータのブラウザから呼出してみました。
ERROR_CONNECTION_REFUSED
 →外のWebは見られます。
 →「localhost」×→「127.0.0.1」×→実IP指定×
 →Windowsファイアーウォールの「許可されたアプリ」にemulator.exeを登録して再実行×
20200206_1.jpg


念のため、再度PC側のブラウザで同じHTMLを呼出してみます。
ただし、Chromeのデベロッパーツールを使わないと送受信テストができないのは不便です。
なので、テスト用のHTMLを修正しました。
----xampp\htdocs\testchat.html---------------------
<html>
<meta charset="utf-8">
<script>
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
console.log("Connection established!");
};

conn.onmessage = function(e) {
var msgtxt = document.getElementById("msgtxt");
console.log(e.data);
msgtxt.innerHTML += "<br>" + e.data;

};

function sendmessage() {
var sndtxt = document.getElementsByName("sndtxt");
conn.send(sndtxt[0].value);
}
</script>
<body>
<h1>WebSocket Test</h1>
WebSocketサーバとのメッセージ通信をテストします。<br>
メッセージ:
<input type="text" name="sndtxt" size="20" maxlength="100">
<input type="button" name="sndbtn" value="Send" onclick="sendmessage()"><br>
以下に送信メッセージ/受信メッセージが表示されます。<br>
<hr>
<div id="msgtxt">
<div>
</body>
</html>
---------------------------------------------------

※<meta charset=・・・>指定なしで、Shift-jisファイルだとエミュレータ上のブラウザで文字化けします。

2)PC側のChromeでテスト用HTMLを試します。
→動きます。ちなみに、Apacheを起動せずにローカルファイル(file://)指定でもOKでした。
20200206_2.jpg

3)PC側のEdgeを動かしてHTMLを試します。
→動きます。こっちはApache起動後じゃないとダメっぽかった。
20200206_3.jpg

よくよく考えたら、『Androidエミュレータ上で「localhost」って言っちゃったら、「エミュレータ上のサーバ」って言う意味になるよな。
で、以下のサイトにその旨が書いてありました。
参考:https://araramistudio.jimdo.com/2018/01/11/android%E3%81%AE%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%8B%E3%82%89%E8%87%AA%E8%BA%AB%E3%81%AEpc-localhost-%E3%81%B8%E6%8E%A5%E7%B6%9A/

母艦PC自身のアドレスは、「10.0.2.2」だそうです。

4)エミュレータ上で「http://10.0.2.2/testchat.html」をブラウズ。
→今度はエラーなしで、HTMLが表示されました。でも[Send]しても無反応。

-ちょっと脱線:そもそも、Android上の「ブラウザ」とは何者?-
参考:https://www.atmarkit.co.jp/ait/articles/1507/17/news032.html
Android Open Source Project(AOSP)ブラウザなのかな?このブラウザが「WebSocket」をサポートしてるか・・・
ブラウザのバージョンは「6.0-・・・」と表示されます。Wikiによると、Android4.4以降サポートしているような・・・
ただしスクリプトコードが共通なのかはよく分かんない。
参考:https://ja.wikipedia.org/wiki/WebSocket


それはともかく、10.0.2.2にすれば進みそう。


◆チャットアプリの修正と再実行
修正は、以下の2か所。
----chatserver.json--------------------------------
【変更前】
"name": "localhost",

【変更後】
"name": "10.0.2.2",
---------------------------------------------------

メッセージ送信処理に純粋にバグがありました。
----Chat.js----------------------------------------
【変更前】
this.ws.send(messages);

【変更後】
messages.map((sendmsg)=> {this.ws.send(sendmsg.text);});
---------------------------------------------------

再実行して、メッセージを入れて[Send]してみたところ、Chrome(testchat.html)側にメッセージが追加されました。
20200206_4.jpg

Chrome側から返信コメントを[Send]してみます。、
スマフォアプリで受信メッセージを表示する処理は未実装でしたね。
よって、デバッガ(Console)で受信を確認しました。
20200206_5.jpg


◆受信メッセージ表示実装
せっかくなので、受信したメッセージの表示を実装してみます。
以下のサイトに参考になるコードがありました。
https://rennnosukesann.hatenablog.com/entry/2018/05/27/155309

内容は以下。
onMessageReceived()メソッド内で受信メッセージをsetStateでthis.stateに追加。
----Chat.js----------------------------------------
【変更前】
//WebSocket:メッセージ受信処理
onMessageReceived = (event) => {
console.log('Receive!', event.data);
}

【変更後】
recMsgid = 2;
avaUrl1 = 'http://' + srvName + '/vtchatsrv/avatar_i.png';
avaUrl2 = 'http://' + srvName + '/vtchatsrv/avatar_other.png';


//WebSocket:メッセージ受信処理
onMessageReceived = (event) => {
console.log('Receive!', event.data);
let recvmsg = {
_id: this.recMsgid,
text: event.data,
createAt: new Date(),
uer: {
_id: 2,
name: 'other user',
avatar: this.avaUrl2,
}
};

this.setState((previousState) => ({
//stateで管理しているメッセージ群に受信メッセージを追加
messages: GiftedChat.append(previousState.messages, recvmsg),
}));
this.recMsgid++;

}
---------------------------------------------------

ちょっと雑な変更かもしれませんが、い・ち・お・う 動きはしました。
以下の感じで出ます。
20200206_6.jpg

ここまでにするのに突っかかったところが何回かありました。備忘。
・アバターのURLをavaUrl1、avaUrl2で設定していますが、パスの途中のフォルダ名を実際の大文字交じり「VTCharSrv」で指定したら、ダメでした。エミュレータの「ブラウザ」でも同様。パス(フォルダ名)は小文字で。常識?
・同じくアバターのavaUrl1をrender()内に「<GiftedChat・・・user={{・・・avater:avaUrl1,}} />」と指定してみましたが、自分のアバターは表示しないのね。
・当初、受信メッセージ・オブジェクトrecmsgの_idを固定値2に指定したら、画面上に受信メッセージが複数回表示される現象になった。_idはメッセージ毎にユニークな値でないといけないらしい。Web上の例では乱数?uuid4()?とかを指定してましたが、さるなので、とりあえず連番を割り当ててます。たぶん、送信メッセージの_idと重複しないようにする必要があるかも。

実用するまでと考えれば以下は最低でも必要でしょう。でも放ったらかしてます。
・メッセージの送信者/アバター/時刻/・・・とかがわかるような形式を作って送受信する必要があるとおもう。それをセットにして表示しないと、自分と自分以外しか判別付かないですよね。



いつ終わるんだ?とか思ってたのですが、それなり?のところまでは割とあっさり行った感じです。
未だ「React Nativeの何たるか」には、近づけてませんが、これをベースに作りたいものを整えたいと思います。

次回からは、(もし続けるとしたら)個別の処理(部分機能)の作り込みの話になるかと・・・

では、今回はこの辺で。
あー、やれやれ。
(m__)m
スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
01 | 2024/02 | 03
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 - -
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-
本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



PVアクセスランキング にほんブログ村 にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR