用Flutter、Socket.io和Node.js打造一对一实时聊天app(下)

上篇文章链接 https://rtcdeveloper.com/t/topic/20401

Flutter——客户端

我们的后端现在已经启动并运行,接下来该创建flutter app了。
创建一个flutter 项目并添加以下依赖项:

  • 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 的逻辑和数据都会被保存。

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()是在AllChatsPageinitState()中调用的,从而在启动时初始化我们的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=users[1]

在另一台设备上重建并运行该app。现在你就可以开启与别人聊天了。:smile:

下一步是什么?
为app构建一个超酷的UI。删除虚拟用户列表并将用户存储在某个数据库中。同时试着为特定用户实现显示在线状态。
快速提示:检查特定房间中的用户数。

如果你碰到任何问题,可以查看我的github repo :

当然了,如果你喜欢我这篇文章,别忘了收藏并点赞。如有任何疑问,评论区见。谢谢 :smile:

原文作者 Ibtesam Ansari*
原文链接 https://medium.com/flutter-community/realtime-chat-app-one-to-one-using-flutter-socket-io-node-js-acd4152c6a00

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
131
相关专栏
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。