Jaguar_websocket結合Flutter搭建簡單聊天室
阿新 • • 發佈:2019-01-09
1.定義訊息
在開始建立webSocket之前,我們需要定義訊息,如:傳送人,傳送時間,傳送人id等..
import 'dart:convert'; class ChatMessageData { final String id; final String msg; final DateTime created; final String name; final int role; ChatMessageData( this.id, this.msg, this.name, this.role,this.created, ); static ChatMessageData formMap(Map map) => ChatMessageData( map['id'], map['msg'], map['name'], map['role'], DateTime.fromMicrosecondsSinceEpoch(map['created'])); Map toMap() => { "id": id, "msg": msg, "name": name, "role":role, "created": created.millisecondsSinceEpoch }; String toJson() => jsonEncode(toMap()); @override String toString() => toMap().toString(); }
我們這裡定義了一個ChatMessageData
,如果你想需要更多欄位,可以再新增
2.新增訊息訂閱
//控制訊息的傳送 final pub = StreamController<ChatMessageData>();//當pub呼叫add(data)方法,該sub的listen會監聽到 final Stream<ChatMessageData> sub = pub.stream.asBroadcastStream();
3. 定義介面
這裡我們定義兩個介面,一個用於連線的介面,一個用於傳送訊息的介面
/mini/login 提交使用者的資訊,如果不正確,返回相關的資訊,不給連線
/min/connect 連線websocket,該介面獲取到websocket物件,然後可以使用該物件
進行傳送訊息
登陸介面
..post('/mini/login', (ctx) async{ User user=await ctx.bodyAsJson(convert: User.forMap); String username = user.username; String password = user.password; if (username.isEmpty || password.isEmpty) { return Response.json(apiJson.errorMsgA(-1, '使用者名稱或密碼為空!').toMap()); } else { User user = await userBean.findOneWhere(userBean.username.eq(username)); if (user == null || user.password != password) { return Response.json(apiJson.errorMsgA(-2, '使用者名稱或密碼不正確!').toMap()); } else { print('使用者:$username登陸成功'); return Response.json(apiJson.successA().toMap()); } } })
連線介面
..ws( '/mini/connect', onConnect: (ctx, ws) { var subscription = sub.listen((ChatMessageData data) { print(data.toJson()); ws.add(data.toJson()); }); ws.done.then((_) { print('使用者已退出聊天房'); subscription.cancel(); }); //連線上之後返回一條資訊 ws.add(new ChatMessageData('1', '歡迎登陸', '伺服器', 1, DateTime.now()).toJson()); }, handler: (data) { //獲取使用者傳送的訊息 ChatMessageData msg=ChatMessageData.formMap(json.decode(data)); print(msg.toJson()); //廣播一條訊息 pub.add(msg); }, )
ok,我們已經搭建好一個簡單的聊天介面了,下面,我們使用Flutter簡單的編輯一下客戶端平臺
4.Flutter建立一個簡單的聊天室
這部分程式碼為Flutter下,可簡單的編輯一個聊天室
mport 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() { runApp(new FriendlychatApp()); } final ThemeData kIOSTheme = new ThemeData( primarySwatch: Colors.orange, primaryColor: Colors.grey[100], primaryColorBrightness: Brightness.light, ); final ThemeData kDefaultTheme = new ThemeData( primarySwatch: Colors.purple, accentColor: Colors.orangeAccent[400], ); const String _name = "Your Name"; class FriendlychatApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: "Friendlychat", theme: defaultTargetPlatform == TargetPlatform.iOS ? kIOSTheme : kDefaultTheme, home: new ChatScreen(), ); } } class ChatMessage extends StatelessWidget { ChatMessage({this.text, this.animationController}); final String text; final AnimationController animationController; @override Widget build(BuildContext context) { return new SizeTransition( sizeFactor: new CurvedAnimation( parent: animationController, curve: Curves.easeOut ), axisAlignment: 0.0, child: new Container( margin: const EdgeInsets.symmetric(vertical: 10.0), child: new Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Container( margin: const EdgeInsets.only(right: 16.0), child: new CircleAvatar(child: new Text(_name[0])), ), new Expanded( child: new Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Text(_name, style: Theme.of(context).textTheme.subhead), new Container( margin: const EdgeInsets.only(top: 5.0), child: new Text(text), ), ], ), ), ], ), ) ); } } class ChatScreen extends StatefulWidget { @override State createState() => new ChatScreenState(); } class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin { final List<ChatMessage> _messages = <ChatMessage>[]; final TextEditingController _textController = new TextEditingController(); bool _isComposing = false; void _handleSubmitted(String text) { _textController.clear(); setState(() { _isComposing = false; }); ChatMessage message = new ChatMessage( text: text, animationController: new AnimationController( duration: new Duration(milliseconds: 700), vsync: this, ), ); setState(() { _messages.insert(0, message); }); message.animationController.forward(); } void dispose() { for (ChatMessage message in _messages) message.animationController.dispose(); super.dispose(); } Widget _buildTextComposer() { return new IconTheme( data: new IconThemeData(color: Theme.of(context).accentColor), child: new Container( margin: const EdgeInsets.symmetric(horizontal: 8.0), child: new Row(children: <Widget>[ new Flexible( child: new TextField( controller: _textController, onChanged: (String text) { setState(() { _isComposing = text.length > 0; }); }, onSubmitted: _handleSubmitted, decoration: new InputDecoration.collapsed(hintText: "Send a message"), ), ), new Container( margin: new EdgeInsets.symmetric(horizontal: 4.0), child: Theme.of(context).platform == TargetPlatform.iOS ? new CupertinoButton( child: new Text("Send"), onPressed: _isComposing ? () => _handleSubmitted(_textController.text) : null, ) : new IconButton( icon: new Icon(Icons.send), onPressed: _isComposing ? () => _handleSubmitted(_textController.text) : null, )), ]), decoration: Theme.of(context).platform == TargetPlatform.iOS ? new BoxDecoration( border: new Border(top: new BorderSide(color: Colors.grey[200]))) : null), ); } Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Friendlychat"), elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0 ), body: new Container( child: new Column( children: <Widget>[ new Flexible( child: new ListView.builder( padding: new EdgeInsets.all(8.0), reverse: true, itemBuilder: (_, int index) => _messages[index], itemCount: _messages.length, ) ), new Divider(height: 1.0), new Container( decoration: new BoxDecoration( color: Theme.of(context).cardColor), child: _buildTextComposer(), ), ] ), decoration: Theme.of(context).platform == TargetPlatform.iOS ? new BoxDecoration(border: new Border(top: new BorderSide(color: Colors.grey[200]))) : null),//new ); } }
上面就是簡單的聊天介面,我們還有主要跟伺服器互動的方法
WebSocket socket; void login() { httpManager.post( url: 'http://192.168.1.101:8080/mini/login', body: json.encode({ "username": "rhyme", "password": "123456", }), onSend: () { //key為scaffold的key scaffoldKey?.currentState ?.showSnackBar(new SnackBar(content: Text('傳送請求,連線伺服器'))); }, onSuccess: (data) { WebSocket.connect('ws://192.168.1.101:8080/mini/connect') .then((socket) { this.socket = socket; socket.listen((data) { //該方法接收伺服器資訊 print(data); Map map = json.decode(data); ChatMessageData msg=ChatMessageData.formMap(map); if(msg.id!=widget.user.uuid){ _handleGetMessage(msg); } }); socket.done.then((e){ //當與伺服器連線中斷呼叫 scaffoldKey.currentState.showSnackBar(new SnackBar(content: Text('連線伺服器中斷!'))); }); }); }, onError: (error) { print(error); scaffoldKey.currentState.showSnackBar( new SnackBar(content: Text('連線失敗!${error.toString()}'))); }); }
我們傳送訊息給服務端
socket.add(new ChatMessageData(widget.user.uuid, value, widget.user.userName, widget.user.role, DateTime.now()).toJson());
最後我們來嘗試一下吧!