用 Flutter、Socket.io 和 Node.js 构建一对一实时聊天 app(上)
Flutter(客户端)
现在,我们的后端已经启动并运行,是时候创建 flutter app 了。
创建一个新的 flutter 项目,在 pubspec.yaml 文件中添加以下依赖项:
- scoped_model: ^1.0.1
- flutter_socket_io: ^0.6.0
创建两个数据类:Message.dart 和 User.dart。
class Message{
final String text;
final String senderID;
final String receiverID;
Message(this.text,this.senderID,this.receiverID);
}
class User{
String name;
String chatID;
User(this.name,this.chatID);
}
然后,创建 ChatModel.dart,里面会保存所有的 socket 逻辑和数据。
import 'package:scoped_model/scoped_model.dart';
import 'package:flutter_socket_io/flutter_socket_io.dart';
import 'package:flutter_socket_io/socket_io_manager.dart';
import 'dart:convert';
import './User.dart';
import './Message.dart';
class ChatModel extends Model {
List<User> users = [
User('IronMan', '111'),
User('Captain America', '222'),
User('Antman', '333'),
User('Hulk', '444'),
User('Thor', '555'),
];
User currentUser;
List<User> friendList = List<User>();
List<Message> messages = List<Message>();
SocketIO socketIO;
void init() {
currentUser = users[0];
friendList =
users.where((user) => user.chatID != currentUser.chatID).toList();
socketIO = SocketIOManager().createSocketIO(
'<ENTER_YOUR_SERVER_URL_HERE>', '/',
query: 'chatID=${currentUser.chatID}');
socketIO.init();
socketIO.subscribe('receive_message', (jsonData) {
Map<String, dynamic> data = json.decode(jsonData);
messages.add(Message(
data['content'], data['senderChatID'], data['receiverChatID']));
notifyListeners();
});
socketIO.connect();
}
void sendMessage(String text, String receiverChatID) {
messages.add(Message(text, currentUser.chatID, receiverChatID));
socketIO.sendMessage(
'send_message',
json.encode({
'receiverChatID': receiverChatID,
'senderChatID': currentUser.chatID,
'content': text,
}),
);
notifyListeners();
}
List<Message> getMessagesForChatID(String chatID) {
return messages
.where((msg) => msg.senderID == chatID || msg.receiverID == chatID)
.toList();
}
}
ChatModel 类不仅包含全部用户、当前用户和该用户的朋友列表的虚拟数据,还包含了所有消息。ChatModel 类中有下列三个方法:
- init() :用来初始化所有变量 和 socket 并向 socket 添加监听器。
- sendMessage() :向服务器发送消息并将服务器添加到消息列表中。
- getMessagesForChatID() :从消息列表中提取与当前对话相关的消息。
接下来,创建 AllChatsPage.dart,该页面会显示所有用户。
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import './ChatPage.dart';
import './User.dart';
import './ChatModel.dart';
class AllChatsPage extends StatefulWidget {
@override
_AllChatsPageState createState() => _AllChatsPageState();
}
class _AllChatsPageState extends State<AllChatsPage> {
@override
void initState() {
super.initState();
ScopedModel.of<ChatModel>(context, rebuildOnChange: false).init();
}
void friendClicked(User friend) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return ChatPage(friend);
},
),
);
}
Widget buildAllChatList() {
return ScopedModelDescendant<ChatModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount: model.friendList.length,
itemBuilder: (BuildContext context, int index) {
User friend = model.friendList[index];
return ListTile(
title: Text(friend.name),
onTap: () => friendClicked(friend),
);
},
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('All Chats'),
),
body: buildAllChatList(),
);
}
}
ChatModel 中的 init() 是从 AllChatsPage 的 initState() 中调用的,会在启动时初始化 app。
然后,我们需创建 ChatPage.dart 来显示消息。
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import './User.dart';
import './Message.dart';
import './ChatModel.dart';
class ChatPage extends StatefulWidget {
final User friend;
ChatPage(this.friend);
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final TextEditingController textEditingController = TextEditingController();
Widget buildSingleMessage(Message message) {
return Container(
alignment: message.senderID == widget.friend.chatID
? Alignment.centerLeft
: Alignment.centerRight,
padding: EdgeInsets.all(10.0),
margin: EdgeInsets.all(10.0),
child: Text(message.text),
);
}
Widget buildChatList() {
return ScopedModelDescendant<ChatModel>(
builder: (context, child, model) {
List<Message> messages =
model.getMessagesForChatID(widget.friend.chatID);
return Container(
height: MediaQuery.of(context).size.height * 0.75,
child: ListView.builder(
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
return buildSingleMessage(messages[index]);
},
),
);
},
);
}
Widget buildChatArea() {
return ScopedModelDescendant<ChatModel>(
builder: (context, child, model) {
return Container(
child: Row(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width * 0.8,
child: TextField(
controller: textEditingController,
),
),
SizedBox(width: 10.0),
FloatingActionButton(
onPressed: () {
model.sendMessage(
textEditingController.text, widget.friend.chatID);
textEditingController.text = '';
},
elevation: 0,
child: Icon(Icons.send),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.friend.name),
),
body: ListView(
children: <Widget>[
buildChatList(),
buildChatArea(),
],
),
);
}
}
最后,打开 main.dart,用下列代码替换原有代码:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import './AllChatsPage.dart';
import './ChatModel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModel(
model: ChatModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: AllChatsPage(),
),
);
}
}
如何检查 app 能否正常运行?
在一台设备上安装并运行 app,然后打开 ChatModel.dart,在 init() 方法中用另一个用户初始化 currentUser,示例如下:
currentUser=users[1]
然后,在另一台设备上重建并运行该 app,就可以与该用户聊天啦!
下一步是什么?
为 app 构建一个酷炫的 UI,删除虚拟用户列表,将用户存储在数据库中。另外,为特定用户显示在线状态。
快速提示:查看特定房间中的用户数量。
如果你在操作中遇到任何问题,可以查看我的 github repo。
如果你喜欢这篇文章,别忘了点赞收藏。
如有任何疑问,欢迎在评论区留言。
感谢大家的阅读!
原文作者:Ibtesam Ansari
原文链接:https://medium.com/flutter-community/realtime-chat-app-one-to-one-using-flutter-socket-io-node-js-acd4152c6a00