把 ChatGPT 加入 Flutter 开发,会有怎样的体验?
前言
ChatGPT 最近一直都处于技术圈的讨论焦点。它除了可作为普通用户的日常 AI 助手,还可以帮助开发者加速开发进度。声网社区的一位开发者"小猿"就基于 ChatGPT 做了一场实验。仅 40 分钟就实现了一个互动直播 Demo。他是怎么做的呢?他将整个过程记录了下来。
(文章转载自开发者的个人博客,以下为正文)
“遇事不决,AI 力学” ~ ChatGPT 可以说是 2023 开年最热门的话题, 它不仅在极短时间内风靡了整个技术圈,更是病毒式地席卷了圈外的各个行业,并对各大企业都起到了实质性影响:
- 谷歌紧急推出 “Bard” 对抗 ChatGPT
- 微软发布新 Bing 集成 ChatGPT
- 复旦发布首个类 ChatGPT 模型 MOSS
- 国内阿里、百度、昆仑万维、网易、京东都开始新一轮 AI 军备
那 ChatGPT 究竟有什么魔力能让“群雄折腰”?这和 ChatGPT 的实现有很大关系:
与以往的统计模型不行,ChatGPT 不是那种「一切都从语料统计里学习」的 AI,相反 ChatGPT 具备有临场学习的能力,业内称之为 in-context learning ,这也是为什么 ChatGPT 可以在上下文中学习的原因。
ChatGPT 属于 AI 领域在商用技术上的重大突破,当然,本篇我们不是要讨论ChatGPT 的实现逻辑,而是 ChatGPT 会怎么样加速我们的开发?
PS:在此之前有人通过指示在 ChatGPT 界面下实现了一个虚拟机,虽然这是一个极端的例子,但是可以很直观地感受到:「ChatGPT 对我们开发的影响是肉眼可见」。
那 ChatGPT 在实际工作中是如何影响我们的开发?为了更直观,下面我们用一个开发场景来模拟这个流程。
基于 ChatGPT 开发
01 开发之前
假设我们现在有一个开发「直播」的需求,那我们可以直接求助 ChatGPT:
「开发一个直播app,是使用第三方SDK好还是自己从0开发好」?
如下图所示,从回答上可以看到,AI 建议我们根据团队实际情况去选择,而在知晓「我的团队只有 5 个人」的情况后,它建议我选择采用 “接入第三方 SDK” 的方式更合理。
那么选择 “接入第三方 SDK” ,接下来的问题就是:「选择做直播,在中国推荐使用哪些厂家的 SDK」?
如下图所示,这个问题 ChatGPT 同样提供了多个选项,从选项里看声网、腾讯云和阿里云好像都符合我们要求,而在接着的「优势问题」对比上看,这三个选项都“不相伯仲”,那我们就在再细化问题。
假设我们希望直播可以有更多“互动能力”,那么把问题修改为 「做互动直播,更推荐使用哪一个厂家的 SDK」 ,截图如下图所示,这次我们得到了更明确的答复,看来声网的 SDK 会更贴合我们的需求。
为了更放心这个选择,我们通过 「声网 SDK 的优势」 和 「什么产品使用了声网SDK」 两个问题进行提问,如下图所示,从回复上看声网作为一个全球化的厂家,在音视频领域还是值得相信。同时,还有包括小米、陌陌等产品都使用了声网的服务。那么就按照 AI 的建议选择声网 SDK 吧。
有没有发现,在获取资料的检索方式上,ChatGPT 确实比搜索引擎更直观且高效。
那么敲定完 SDK ,接下来我们需要选择应用的开发框架,我们把需求限定在 Android 和 iOS,更好是能兼容 Web,覆盖整个移动端 ,因为团队人数不多,所以我们希望采用跨平台开发来节约成本,那么问题就是:
「移动端哪个跨平台框架更适合做直播」?
如下图所示,得到的答案有 React Native 和 Flutter ,而恰好在 Flutter 回复里可以看到声网 SDK 的存在,所以我们可以敲定 App 开发框架就选 Flutter 了。
最后,在开发之前,我们还需要继续提问 「如何获取声网 SDK 」 和 「使用声网 SDK 需要做什么」,这样我们就可以在开始开发之前提前准备好需要的东西,包括注册账号、下载 SDK 等。
关于注册获取 App ID 等步骤这里就省略了,毕竟目前这部分 ChatGPT 也无能为力。
02 开始开发
那么到这里我们就假定大家已经准备好了开发环境,接下来可以直接进行开发。
我们还是继续面向 ChatGPT 开发,首先我们的提问是:「用声网的 Flutter SDKagora_rtc_engine 6.1.0 写一个视频通话页面,给我 dart 代码」 ,结果如下 GIF 所示,可以看到 ChatGPT 开始了疯狂的输出:
为什么关键词是「视频通话」?因为它比直播场景更精准简单,生成的代码更靠谱(经过提问测试),而基于视频通话部分,后面我们可以快速拓展为互动直播场景;而指定版本是为了避免 AI 使用旧版本 API。
从上门的代码生成可以看到,ChatGPT 生产的代码是自带中文注释,更贴心的是,如下图所示,在生成的代码末尾还给你解释了这段代码的实现逻辑,就像一个“知心大姐姐”。
从这里也可以感觉到 ,ChatGPT 不是一个单纯的完全只会基于语料答复整合的 AI 。
当然,直接复制生成的代码后会发现这段代码会报错,这和 ChatGPT 目前的模型数据版本有一定关系,所以针对生成的代码我们需要做一定手动调整,比如:
- 采用 createAgoraRtcEngine 和 initialize 创建和初始化 RtcEngine
- 将 setEventHandler 修改为最新的 registerEventHandler
- 将 AgoraRenderWidget 修改为 AgoraVideoView
最后修改代码如下,其中 80% 以上的逻辑都来自 ChatGPT 的自动生成,虽然没办法做到“直出”,这无疑大大提高了开发的生产力。
class VideoCallPage extends StatefulWidget {
final String channelName;
const VideoCallPage({Key? key, required this.channelName}) : super(key: key);
@override
_VideoCallPageState createState() => _VideoCallPageState();
}
class _VideoCallPageState extends State<VideoCallPage> {
late RtcEngine _engine;
bool _localUserJoined = false;
bool _remoteUserJoined = false;
int? rUid;
@override
void initState() {
super.initState();
initAgora();
}
@override
void dispose() {
_engine.leaveChannel();
super.dispose();
}
Future<void> initAgora() async {
await [Permission.microphone, Permission.camera].request();
_engine = createAgoraRtcEngine();
await _engine.initialize(RtcEngineContext(
appId: config.appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
_engine.registerEventHandler(RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {
_localUserJoined = true;
});
},
onUserJoined: (connection, remoteUid, elapsed) {
setState(() {
_remoteUserJoined = true;
rUid = remoteUid;
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
setState(() {
_remoteUserJoined = false;
rUid = null;
});
},
));
await _engine.enableVideo();
await _engine.startPreview();
await _engine.joinChannel(
token: config.token,
channelId: widget.channelName,
uid: config.uid,
options: const ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("VideoCallPage"),),
body: Center(
child: Stack(
children: [
_remoteUserJoined ? _remoteVideoView(rUid) : _placeholderView(),
_localUserJoined ? _localVideoView() : _placeholderView(),
],
),
),
);
}
Widget _placeholderView() {
return Container(
color: Colors.black,
);
}
Widget _remoteVideoView(id) {
return AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: id),
connection: RtcConnection(channelId: widget.channelName),
),
);
}
Widget _localVideoView() {
return Positioned(
right: 16,
bottom: 16,
width: 100,
height: 160,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
),
),
);
}
}
接下来,如下图所示,在将项目运行到手机和 PC 端之后,可以看到我们就完成了最简单的直播视频场景,而基于我们打算做直播的念头仅仅过去了 40 分钟,这其中还包含了注册声网账号和申请 App ID 的过程,我们通过简单的提问、复制、粘贴、修改,就完成了一个直播需求的 demo。
红色方块是后期加上的打码~
那么到这里,虽然目前为止 demo 项目还不是互动直播,但是基于这个 demo 实现互动直播场景不会太难,因为你已经跑通了整个 SDK 的链路流程了。
03 进阶开发
那假设我们需要继续往互动直播的方向开发,那么我们肯定会遇到“互动”这个需求,比如「收到用户发送的一段内容后画面弹出一个动画」 这样的需求。
那么首先我们要知道声网 SDK 如何监听用户发送的内容,所以接下来我们继续提问:「如何使用声网的 agora_rtc_engine 6.1.0 监听别人发送的文本消息」 ?
这里为什么还强制写 agora_rtc_engine 6.1.0 ?因为如果不写,默认可能会输出 4.x 版本的老 API。
尽管得到的答案并不是 Dart 代码而是 OC ,但是关键词 registerEventHandler 和 Message 我们捕抓到了,简单对比一下,就是 Flutter SDK 里的 registerEventHandler 对象,可以发现平替的接口就是 onStreamMessage 回调。
那么接着就是弹出什么内容,因为我们没有素材,假设还没有设计师,那不如就让 ChatGPT 帮我们画一只兔子吧,不过测试结果并不好,如下图所示,从输出结果上看 ,这并不是我们想要的。
这里是我自己加的粉色,不然都是白色会糊成一坨,不得不说 ChatGPT 在绘制能力上“很抽象”。
所以 ChatGPT 有时候也不是很智能,可能目前在绘画理解上它还没那么成熟, 但是没问题, ChatGPT 是可以通过上下文学习“调教”的,比如我们觉得兔子的耳朵形状太离谱,那么我们可以让 ChatGPT 给我们调整。
如下所示,虽然调整之后依然不对,但是比起一开始是不是好很多了?
这就是 ChatGPT 在每次会话上下文里学习的表现。
然后我们在兔子耳朵的基础上再让 ChatGPT 补全兔子头,虽然最终的效果依然不理想,但是比起一开始已经进步了很多。
同时我们还让 ChatGPT 给我们画了一个“星星”,然后结合这两个 Canvas 绘制的素材,我们在代码里设置接收到 "兔子" 和 星星 文本的时候,就弹出一个放大动画效果。
最终运行后效果如下 GIF 所示,看起来很简陋,但是要知道,我们只是经过了简单的复制/粘贴就完成了这样的效果,这难道不是开发效率的极大提高?
源码在后面。
来自 ChatGPT 的兔子头代码:
class BunnyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 设置画笔
final paint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final path = Path();
// 绘制左耳
path.moveTo(size.width / 2 - 40, size.height / 2 - 80);
path.lineTo(size.width / 2 - 60, size.height / 2 - 120);
path.quadraticBezierTo(size.width / 2 - 70, size.height / 2 - 135,
size.width / 2 - 50, size.height / 2 - 160);
path.lineTo(size.width / 2 - 30, size.height / 2 - 120);
path.quadraticBezierTo(size.width / 2 - 40, size.height / 2 - 100,
size.width / 2 - 40, size.height / 2 - 80);
// 绘制路径
canvas.drawPath(path, paint);
final path2 = Path();
// 绘制右耳
path2.moveTo(size.width / 2 + 40, size.height / 2 - 80);
path2.lineTo(size.width / 2 + 60, size.height / 2 - 120);
path2.quadraticBezierTo(size.width / 2 + 70, size.height / 2 - 135,
size.width / 2 + 50, size.height / 2 - 160);
path2.lineTo(size.width / 2 + 30, size.height / 2 - 120);
path2.quadraticBezierTo(size.width / 2 + 40, size.height / 2 - 100,
size.width / 2 + 40, size.height / 2 - 80);
// 绘制路径
canvas.drawPath(path2, paint);
final path3 = Path();
// 绘制头部
final rect =
Rect.fromLTWH(size.width / 2 - 60, size.height / 2 - 140, 120, 120);
path3.addOval(rect);
// 绘制路径
canvas.drawPath(path3, paint);
final path4 = Path();
// 绘制眼睛
final leftEyeCenter = Offset(size.width / 2 - 20, size.height / 2 - 80);
final rightEyeCenter = Offset(size.width / 2 + 20, size.height / 2 - 80);
final eyeRadius = 8.0;
path4.addArc(
Rect.fromCircle(center: leftEyeCenter, radius: eyeRadius), 0, pi * 2);
path4.addArc(
Rect.fromCircle(center: rightEyeCenter, radius: eyeRadius), 0, pi * 2);
// 设置画笔
final paint2 = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
// 绘制路径
canvas.drawPath(path4, paint2);
final path5 = Path();
// 绘制鼻子
final noseCenter = Offset(size.width / 2, size.height / 2 - 50);
final noseRadius = 10.0;
path5.addArc(
Rect.fromCircle(center: noseCenter, radius: noseRadius), 0, pi * 2);
// 绘制路径
canvas.drawPath(path5, paint2);
}
@override
bool shouldRepaint(BunnyPainter oldDelegate) => false;
}
来自 ChatGPT 的星星代码:
class StarPaint extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: HeartPainter(),
size: Size(50, 50),
);
}
}
class StarPaint extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final path = Path();
final halfWidth = size.width / 2;
final halfHeight = size.height / 2;
final radius = halfWidth;
path.moveTo(halfWidth, halfHeight + radius);
path.arcToPoint(
Offset(halfWidth + radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight - radius),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth - radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight + radius),
radius: Radius.circular(radius),
clockwise: true,
);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant StarPaint oldDelegate) {
return false;
}
}
自己补充的监听文本、发送文本和动画效果代码:
onStreamMessage: (RtcConnection connection, int remoteUid, int streamId,
Uint8List data, int length, int sentTs) {
var message = utf8.decode(data);
if (message == "兔子") {
showDialog(
context: context,
builder: (context) {
return AnimaWidget(Rabbit());
});
} else if (message == "星星") {
showDialog(
context: context,
builder: (context) {
return Center(
child: AnimaWidget(StarPaint()),
);
});
}
Future.delayed(Duration(seconds: 3), () {
Navigator.pop(context);
});
},
Future<void> _onPressSend() async {
try {
final streamId = await _engine.createDataStream(
const DataStreamConfig(syncWithAudio: false, ordered: false));
var txt = (Random().nextInt(10) % 2 == 0) ? "星星" : "兔子";
final data = Uint8List.fromList(utf8.encode(txt));
await _engine.sendStreamMessage(
streamId: streamId, data: data, length: data.length);
} catch (e) {
print(e);
}
}
class AnimaWidget extends StatefulWidget {
final Widget child;
const AnimaWidget(this.child);
@override
State<AnimaWidget> createState() => _AnimaWidgetState();
}
class _AnimaWidgetState extends State<AnimaWidget> {
double animaScale = 1;
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 1), () {
animaScale = 5;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: animaScale,
duration: Duration(seconds: 1),
curve: Curves.bounceIn,
child: Container(child: widget.child));
}
}
相信到这里大家应该可以感受到 ChatGPT 提高开发效率的魅力,甚至你还可以把 ChatGPT 集成到你的直播场景里,通过 Flutter 上的 chatgpt_api_client 插件,你可以在 App 里直接向 ChatGPT 提问,比如通过 OpenAI 的 API 实现一个可以互动的虚拟主播。
我怎么知道这个插件?肯定也是问 ChatGPT 的啊~
04 最后
到这里,相信大家应该能感受到,在使用 ChatGPT 之后,整个开发效率能够得到很大的提升,特别是内容检索的高效和准确上比搜索引擎更加靠谱,另外也能帮我们完成一些“体力活”形式的代码。
当然我们也看到了目前 ChatGPT 并不能完全替代人工,因为它在很多方面生成的内容并不完美,特别是很多代码还是需要我们人工调整,但是这并不影响 ChatGPT 的价值。
最后引用我曾经看到过的关于 ChatGPT 的一些评价:
「当你抱怨 ChatGPT鬼话连篇满嘴跑火车的时候,这可能有点像你看到一只猴子在沙滩上用石头写下1+1=3。它确实算错了,但这不是重点。它有一天会算对的。」
我相信 AI 并不是直接取代人类的方式,因为它对社会的挤压不是从水平上碾压,而是劣币驱逐良币,比如有位大佬就说过:「乙方最讨厌甲方什么都不懂还bb,但乙方的议价权恰恰来源于甲方什么都不懂还 bb」 ,而现在 ChatGPT 在慢慢消磨掉整个议价权。
总的来说「ChatGPT 只是一个产品,它不代表的整个技术的“上限” ,它代表的是技术已经到达商用的临界点」。
现在,它在慢慢成为开发圈子里的习惯,和曾经的 Copilot 一样,而同时它在其他领域如文字编排等的能力,甚至远超它在开发领域的价值。
欢迎开发者们也尝试体验声网 SDK,实现实时音视频互动场景。现注册声网账号下载 SDK,可获得每月免费 10000 分钟使用额度。如在开发过程中遇到疑问,可在声网开发者社区与官方工程师交流。
把 ChatGPT 加入 Flutter 开发,会有怎样的体验?的更多相关文章
- 浅谈Flutter(一):搭建Flutter开发环境
学习内容来自: Flutter中文网 . Flutter实战 -------------------------------------------------------------------- ...
- 安装与配置Flutter开发环境
这篇博客我们介绍了Flutter,并且对比了H5,React Native,Flutter. 由于Flutter是跨平台的开发框架,开发一次可以同时运行在Android和iOS上面,所以我们开发时最好 ...
- MAC安装flutter开发环境
#最近在学flutter开发,写一篇记录一下安装的过程 1.配置flutter镜像地址 vim ~/.bash_profile 命令行输入后回车,打开.bash_profile配置镜像地址 expo ...
- Flutter开发环境(Window)配置及踩坑记录
Flutter 是 Google 用以帮助开发者在 iOS 和 Android 两个平台开发高质量原生 UI 的移动 SDK.Flutter 兼容现有的代码,免费且开源,在全球开发者中广泛被使用. F ...
- Flutter开发中的几个常用函数
几个Flutter开发中的常用函数 /** 返回当前时间戳 */ static int currentTimeMillis() { return new DateTime.now().millisec ...
- [Dart] Flutter开发中的几个常用函数
几个Flutter开发中的常用函数 /** 返回当前时间戳 */ static int currentTimeMillis() { return new DateTime.now().millisec ...
- Flutter 开发环境搭建
Flutter 开发环境搭建 官方的资料相对还是比较全面的,包含了很多中文的资料信息.官方对咱们国家的开发人员还是很友好的. 安装教程:https://flutter.io/get-started/i ...
- mac 上配置flutter开发环境
(ios,Android,Xcode,Android Studio,VScode,IDEA) 1)安装Flutter SDK 2)iOS 环境配置 3)Android Studio配置 4)VS co ...
- 重磅开源|AOP for Flutter开发利器——AspectD
https://github.com/alibaba-flutter/aspectd 问题背景 随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品.但在我们 ...
- 搭建Flutter开发环境需要注意的几个小Tips
目录 下载SDK 安装 Android Stdio + SDK + tool SDK + 创建模拟器 + 插件(flutter和dart) Xcode + cocoapods VSCode + Flu ...
随机推荐
- error Delete `␍` prettier/prettier 错误解决方案
问题根源: 罪魁祸首是git的一个配置属性:core.autocrlf 由于历史原因,windows下和linux下的文本文件的换行符不一致. Windows在换行的时候,同时使用了回车符CR(car ...
- 全文检索引擎:solr lucene
solr在lucene外边做了一层厚厚的封装,主要是为了简化二次开发,提供了一些成熟的解决方案. Lucene是全文检索是对索引中Document的各field进行匹配,可返回document,得到查 ...
- calibredrv命令
flattencell: set L1 [layout create *.gds -dt_expand] $L1 flatten cell TOP_CELL_NAME $L1 gdsout ./*_f ...
- mybatis-generator 生成实体类,表字段类型有text,longtext,生成**WithBLOBs解决办法
modeType="flat" 其他参数请查询文档http://www.youbiji.cn/doc/mybatis/xml-table.html
- Leetcode本地阅读器开发--01界面设计一
返回项目声明及目录:Leetcode本地阅读器开发--总声明 整个界面设计如下:后续可能会不断优化和加入新功能 1.启动后界面 2.进行具体题目搜索 3.进行分类搜索 本节主要介绍程序界面的绘制: 1 ...
- oracle中查询表字段信息及主键字段
select a.owner, a.table_name, a.column_name, a.data_type, d.constraint_type, a.num_nulls from all_ta ...
- Unity中常用的几种读取本地文件方式
使用的命名空间如下 using LitJson;using System.Collections.Generic;using System.IO;using System.Text;using Uni ...
- 第一讲:selenium快速入门
一.selenium目前住主流的web自动化测试框架: 1.资料丰富 资料丰富 2.测试岗位招聘要求,上板率非常之高 3.支持多语言(iava/ pythan/ go /js) ...
- SpringCloud框架开发
1.是什么微服务 是一种架构模式,他提倡将单一应用程序划分一组小的服务,服务之间的相互配合.互相协调. 2.Spring Cloud简介 SpringClound等于分布式微服务架构的一站式解决方案, ...
- clamav测试用例 API
最近接触到clamav这一块,本身是一个很简单的任务,只需要调用他的API对文件进行检测即可,但是在进行大量搜索发现,网上最多只有API的讲解,且质量层次不齐,这可为难住我了,作为一个名副其实的&qu ...