[教程]带有Go聊天服务器的异步Flutter聊天客户端,该客户端由gRPC提供支持(简单和流式传输)

几周前,我为自己打开了Flutter。 它极大地改变了移动应用程序的开发。 我现在称呼它为“愉快的发展”。

我们将在本文中实现以下方案:

  • 移动聊天应用程序允许用户编写文本消息,然后将消息发送到服务器,并异步接收来自服务器的回声答案。 移动聊天应用程序是使用Dart lang和Flutter框架开发的。
  • 聊天回声服务器是使用Go语言开发的。 它通过简单的gRPC Send方法从客户端接收消息。 服务器通过gRPC服务器端传输方法Subscribe向客户端重播回显。
  • 我们将使用protobuf定义用于客户端-服务器通信的API。

这是我们的Flutter聊天客户端应用程序的外观:

您可以在此处获得本文的完整源代码:

amsokol / flutter-grpc-tutorial
通过在GitHub上创建一个帐户,为amsokol / flutter-grpc-tutorial开发做出贡献。 github.com

先决条件

  • 开始之前,我们必须已安装并配置Go v1.11 +。 我们将使用Go模块的功能。
  • 我们必须安装Dart 2的Server SDK。尽管Flutter包含Dart SDK,我们仍需要独立安装才能根据protobuf API定义生成Dart文件。
  • 我们必须安装Flutter v1.0 +。
  • 我们必须安装protobuf编译器。 在此处查看详细信息。
  • 接下来为Proto编译器安装Go代码生成器插件:
 去获取-u github.com/golang/protobuf/protoc-gen-go 
  • 最后一步是为Dart安装protoc插件:
pub global activate protoc_plugin 

项目骨架

我们将使用以下项目结构:

  flutter-grpc-tutorial根文件夹
|
| ----- api原始API定义
| ----- flutter_client聊天客户端应用程序
| ----- 转到服务器回显聊天服务器
| ----- third_party需要其他文件才能从原始协议编译Go和Dart文件

第三方档案

我们需要从此处将empty.prototimestamp.protowrappers.proto下载到flutter-grpc-tutorial / third_party / google / protobuf文件夹。

注意:文件protoc-gen.cmd是可选的-不用管它。 它是Windows原型编译脚本的示例。

定义客户端-服务器通信API

api部分仅包含一个具有ChatService定义的文件:

聊天服务器

第一步是根据proto API定义生成Go protobuf / grpc代码。 我们必须先创建输出文件夹:

  cd flutter-grpc-tutorial 
  mkdir -p go-server / pkg / api / v1 

然后生成Go代码:

  protoc chat.proto --proto_path = api / proto / v1 --proto_path =。  --go_out = plugins = grpc:go-server / pkg / api / v1 

代码生成的结果应如下所示:

接下来,我们必须创建go-server / pkg / service / v1 / chat.go文件-ChatService实现。 服务器从客户端接收消息并将其存储到通道。 Subscribe方法从通道获取消息,并将回显发送回客户端。 实现非常简单:

请在此处获取go-server的完整源代码。

创建Flutter客户端应用程序项目

我已将此官方教程用作Flutter客户端的入门文章:

用Flutter构建漂亮的UI
Flutter拥有丰富的Material Design和Cupertino(iOS),可轻松开始构建精美的应用程序… codelabs.developers.google.com

我强烈建议您学习本课。 它可以帮助您了解如何使用Flutter开发出色的UI应用程序。

注意1:为了使代码简单,我避免执行“针对iOS和Android定制”步骤。

注意2:我将main.dart文件拆分为多个文件。 我更喜欢几个小代码文件,而不是一个大文件。

您可以在此处获取我们Flutter客户端应用程序的源代码。

在开始之前,让我们打开pubspec.yaml文件以查看我们正在使用的Dart程序包。 这是依赖项部分:

grpcprotobuf软件包为Dart语言提供了gRPC引擎。 uuid用于为聊天消息生成唯一的ID。

第一步是根据proto API定义生成Dart protobuf / grpc代码。 我们必须先创建输出文件夹:

  cd flutter-grpc-tutorial 
  mkdir -p flutter_client / lib / api / v1 / google / protobuf 

然后,为protobuf支持文件和chat.proto生成Dart代码:

  protoc empty.proto timestamp.proto wrappers.proto --proto_path = third_party / google / protobuf --plugin = protoc-gen-dart =%USERPROFILE%/ AppData / Roaming / Pub / Cache / bin / protoc-gen-dart.bat --dart_out = grpc:flutter_client / lib / api / v1 / google / protobuf 
  protoc chat.proto --proto_path = api / proto / v1 --proto_path = third_party --plugin = protoc-gen-dart =%USERPROFILE%/ AppData / Roaming / Pub / Cache / bin / protoc-gen-dart.bat- -dart_out = grpc:flutter_client / lib / api / v1 

注意:仅Windows需要参数plugin = protoc-gen-dart =%USERPROFILE%/ AppData / Roaming / Pub / Cache / bin / protoc-gen-dart.bat 。 对于MacOS和Linux,请忽略此选项。

生成的结果应如下所示:

大。 让我们看一下其他Dart文件:

主镖

然后,它使用从原型文件ChatServiceClient存根生成的消息发送到服务器。 如果成功,它将引发onSentSuccess事件,消息事件状态已更新(从UNKNOWNSENT ):

如果发生错误,它将引发onSentError事件,使客户端连接无效并尝试再次发送。 当应用程序关闭时,它将停止尝试发送消息:

接下来看一下startListening方法。 它还创建到服务器的客户端连接通道:

然后,它使用从原始文件ChatServiceClient存根生成的gRPC流打开,并开始侦听传入的消息。 当消息到达时,它将引发onReceivedSuccess事件:

如果发生错误或关闭流,则会引发onReceivedError事件,使客户端连接无效,尝试再次打开并侦听流。 当应用程序关闭时,它将停止尝试监听传入的消息:

chat_screen.dart

键入消息文本并按“发送”按钮时,将引发_handleSubmitted事件。 它根据输入文本创建新的传出消息,通过带宽缓冲区显示该消息并将消息发送到服务器。 发送消息是异步操作,因此我们目前不知道结果:

这是我们处理聊天客户端服务事件的方式。 这是非常简单的代码:

接下来, 构建方法的一部分演示了我们如何在移动设备屏幕上显示消息:

我们使用功能强大的StreamBuilder类通过从多个来源向流发送数据来显示聊天消息。 源是事件处理程序_handleSubmitted,onSentSuccess,onReceivedSuccess。

_addMessages方法更新现有外发消息的状态,或将新的外发或传入消息添加到消息列表:

然后,ListView构建器使用结果消息列表中的消息创建ListView小部件:

因此,我们只剩下一个重要的话题-带宽缓冲区。 看一下我在Flutter GitHub存储库中创建的问题:

StreamBuilder删除一些使用StreamController添加的值·问题#26375·颤振/颤振
嗨,非常感谢Flutter! 代码:import’dart:async’; 导入’package:flutter / material.dart’; void main()=>… github.com

感谢Flutter团队的zoechi的回答。 根据他的建议,我尝试使用stream_transform Dart包中的debounceBuffer。 不幸的是,它对我不起作用。 这就是为什么我开发了简单的BandwidthBuffer类。 它会累积消息,并在您在类构造函数中指定的时间段内将消息发送给StreamBuilder不超过一次。

此代码使用500毫秒持续时间参数创建缓冲区:

这是将消息发送到缓冲区的方法:

此事件每500毫秒引发一次,以通过StreamController对象将消息发送到StreamBuilder:

这是width_buffer.dart的代码:

让我们向服务器发送几条消息:

有用!

但这还没有结束。 我们的Flutter聊天应用程序具有一定的容错能力。 它可以在离线模式下工作。 为确保这一点,请关闭服务器并再次键入消息:

看,我们的消息被标记为发送。 然后再次启动服务器,您将看到发生了什么。 在Dart gRPC客户端重新连接到服务器之前,可能要花费一些时间:

目前为止就这样了。 完整的源代码在这里。

谢谢!