前言

我们在前面花了很大篇幅介绍 Provider 状态管理,这是因为在 Flutter 中,Provider 是众多状态管理插件的首选。本篇以即时聊天为例,来讲述 Provider 的综合应用,也算是 Provider 状态管理系列的终结篇。本篇涉及的内容如下:

  • 联系人界面的构建;
  • 聊天界面的简单实现;
  • StreamProvider 接收 Socket流数据并自动通知界面刷新;
  • MultiProvider为聊天主界面提供多个Provider状态;
  • 多个 Provider存在交叉数据时处理方式。

联系人界面构建

我们在聊天前,需要选择对应的联系人进行单聊,因此需要构建一个联系人列表。这里我们使用简单的 ListView.builder Mock 数据构建联系人列表。界面如下所示,其中关键的就是点击联系人时将联系人的 id通过路由传递过去,以便发送消息时通过用户 id指定接收用户。

return ListTile(
  leading: _getRoundImage(contactors[index].avatar, 50),
  title: Text(contactors[index].nickname),
  subtitle: Text(
    contactors[index].description,
    style: TextStyle(fontSize: 14.0, color: Colors.grey),
  ),
  onTap: () {
    debugPrint(contactors[index].id);
    RouterManager.router.navigateTo(context,
        '${RouterManager.chatPath}?toUserId=${contactors[index].id}');
  },
);

聊天界面的实现

我们将发送的消息放在右边,将接收到的消息放在左边,居左还是居右通过 Container 的 margin 来实现。至于区分,通过消息对象的fromUserId 来区分,如果 fromUserId 和当前用户id 一致,则是发送出去的消息,否则就是接收到的消息。在这里我们因为还没有用户体系,先将当前的用户 id 写死。为了实现模拟器之间的聊天,我们一个模拟器设置为 user1,一个设置为 user2。界面也是使用ListView.builder(万能不?)构建。

return ListView.builder(
  itemBuilder: (context, index) {
    MessageEntity message = messages[index];
    double margin = 20;
    double marginLeft = message.fromUserId == 'user1' ? 60 : margin;
    double marginRight = message.fromUserId == 'user1' ? margin : 60;
    return Container(
      margin: EdgeInsets.fromLTRB(marginLeft, margin, marginRight, margin),
      padding: EdgeInsets.all(15),
      alignment: message.fromUserId == 'user1'
          ? Alignment.centerRight
          : Alignment.centerLeft,
      decoration: BoxDecoration(
          color: message.fromUserId == 'user1'
              ? Colors.green[300]
              : Colors.blue[400],
          borderRadius: BorderRadius.circular(10)),
      child: Text(
        message.content,
        style: TextStyle(color: Colors.white),
      ),
    );
  },
  itemCount: messages.length,
);

聊天界面的一个特点是会接收StreamProvider 推送的最新的消息,为了统一,我们将接收消息和发送消息都通过StreamProvider推送更新界面。

// 发送消息时将消息加入到流控制器中
void sendMessage(String event, T message) {
  _socket.emit(event, message);
  _socketResponse.sink.add(message);
}

// 接收消息时也加入到流控制器中
_socket.on(recvEvent, (data) {
  _socketResponse.sink.add(data);
});

这样不管是接收消息还是发送消息都会通过 StreamProvider 重新构建聊天界面。那问题来了,聊天列表数据如何刷新呢?

消息界面的 MultiProvider

消息界面需要接收 StreamProvider 的消息流,还需要使用消息列表数据,这里我们使用了 MultiProvider。其中消息发送框和聊天界面共用 ChatMessageModel(仅为演示,实际可以拆分开)。

final chatMessageModel = ChatMessageModel();
//...
body: Stack(
  alignment: Alignment.bottomCenter,
  children: [
    MultiProvider(
      providers: [
        StreamProvider<Map<String, dynamic>?>(
            create: (context) => streamSocket.getResponse,
            initialData: null),
        ChangeNotifierProvider.value(value: chatMessageModel)
      ],
      child: StreamDemo(),
    ),
    ChangeNotifierProvider.value(
      child: MessageReplyBar(messageSendHandler: (message) {
        Map<String, String> json = {
          'fromUserId': 'user1',
          'toUserId': widget.toUserId,
          'contentType': 'text',
          'content': message
        };
        streamSocket.sendMessage('chat', json);
      }),
      value: chatMessageModel,
    ),
]

//...

其中ChatMessageModel即消息列表状态数据,里面只有一个消息对象数组和一个添加消息方法,以及一个 content 属性是给消息回复框使用的。

这里就有一个问题,StreamProvider 推送 StreamSocket过来的消息的时候, ChatMessageModel 其实是不知道的。如果要知道,一个办法就是在 StreamSocket 引用 ChatMessageModel对象,然后调用其 addMessage 方法添加消息。但是这样会增加两个类的耦合。还有一种方式是取巧的方式了,那就是 StreamdDemo的 build 方法能够获取到 StreamSocket 推送的最新消息,在这里读取到最新的消息后就可以添加到消息列表了。由于前面我们发送消息和接收消息都将消息加入到了消息流中,这样处理方式就统一了。

这种方式需要注意,Provider 不允许在组件的build 方法中再次调用类似 notifyListeners 的方法通知该组件刷新,因此在 ChatMessageModel的 addMessage 方法里不可以使用notifyListeners来通知组件刷新,否则会出现同一组件刷新冲突。实际上,因为另一个Provider 已经通知该组件刷新了,因此也没必要再通知了。当然,这仅仅是一种取巧方法,假设这个addMessage 方法还需要通知其他组件刷新,那这种形式就就不可取了。

class ChatMessageModel with ChangeNotifier {
  List<MessageEntity> _messages = [];
  List<MessageEntity> get messages => _messages;

  String content = '';

  void addMessage(Map<String, dynamic> json) {
    _messages.add(MessageEntity.fromJson(json));
  }
}

这里我们先不考虑这种情况,StreamDemo 的 build关于这部分的处理方法如下,这里对于吧 ChatMessageModel 也就不需要使用 watch 方法了,完全依赖于 StreamProvider 的流推送来更新组件。每次发送消息或接收消息后,构建时在返回组件树前就更新了消息列表数据了,因此也能保证数据是最新的。其实,相当于我们投机取巧实现了两个 Provider之间的数据交互。

@override
Widget build(BuildContext context) {
  Map<String, dynamic>? messageJson = context.watch<Map<String, dynamic>?>();
  if (messageJson != null) {
    context.read<ChatMessageModel>().addMessage(messageJson);
  }
  List<MessageEntity> messages = context.read<ChatMessageModel>().messages;
  // ListView 部分
}

运行效果

来看一下运行效果,模拟器的好处就是可以开多个调试。效果是实现了,不过实际即时聊天比这个复杂很多,而且一般也不会用 Socket,但是如果 App 内部要实现应用打开后的即时消息推送,WebSocket 是一个不错的选择。源码已经提交,后端和Flutter 代码分布如下:

Flutter Provider 部分代码(null safety 版本)

后端代码(Express 版本)

以上就是Android Flutter基于WebSocket实现即时通讯功能的详细内容,更多关于Flutter WebSocket通讯的资料请关注Devmax其它相关文章!

Android Flutter基于WebSocket实现即时通讯功能的更多相关文章

  1. 五分钟学会HTML5的WebSocket协议

    这篇文章主要介绍了五分钟学会HTML5的WebSocket协议,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. 前端监听websocket消息并实时弹出(实例代码)

    这篇文章主要介绍了前端监听websocket消息并实时弹出,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  7. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  8. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  9. ios – Websockets可以在移动电话上工作吗?

    相关地,我怀疑长轮询客户端可能是实现类似功能的好方法,但我想知道我可能遇到的移动特定问题.到目前为止,我已经读过长时间的轮询请求可能会对电池寿命产生相当大的影响.我还听说iOS以某种方式限制了对单个服务器的连接数量,这可能是个问题.有没有人在使用实时组件的移动应用程序上工作?

  10. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部