1. 程式人生 > >Jaguar_websocket結合Flutter搭建簡單聊天室

Jaguar_websocket結合Flutter搭建簡單聊天室

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());

最後我們來嘗試一下吧!