Flutter初体验:用一套代码搞定三端,北漂码农的自救指南
上周五晚上十点半,我瘫在工位上盯着屏幕发呆。产品经理刚甩过来一个新需求:“咱们得有个独立App,iOS、Android、还有鸿蒙都得覆盖,下个月初上线。”我差点把键盘砸了——我们组就三个前端,一个还在休产假,剩下的我和老王还在维护那个祖传的React Native项目,上次发版还因为Android 14兼容性问题被用户骂上应用商店差评榜。
房贷还款日就在下周,跳槽是不可能跳槽的。思来想去,我点开了最近刷到的Flutter宣传视频,心里嘀咕:要不……试试这个?
为什么是Flutter?
说实话,我对跨平台框架一直有PTSD。之前那个React Native项目,光是处理不同Android厂商的WebView兼容性问题就让我掉了半斤头发。但这次情况特殊——公司连外包预算都没批,纯靠我们自己“挤”时间搞。我翻了翻GitHub趋势榜,又看了几个大厂的落地案例,咬咬牙决定赌一把Flutter。
选型理由很现实:
- Dart语言学习成本低:我之前用过Java和TypeScript,Dart的语法糖看起来很友好
- 热重载真的香:再也不用每次改个颜色都要等五分钟编译
- 自带渲染引擎:告别原生组件碎片化问题(至少理论上是这样)
最重要的是,隔壁组的老李去年用Flutter做了个内部工具,据说开发效率提升了一倍。虽然他现在天天抱怨Dart的空安全检查烦人,但好歹项目按时上线了不是?
环境搭建:比想象中顺利
本来以为配置环境会踩坑,毕竟之前配React Native时光是Android SDK版本就折腾了两天。但这次打开Flutter官网,跟着文档一步步走,居然一次成功。我的MacBook Pro M1(去年咬牙贷款买的,现在想想真是冲动)跑起来特别快。
# 安装Flutter SDK
git clone https://github.com/flutter/flutter.git -b stable
export PATH="$PATH:`pwd`/flutter/bin"
# 验证安装
flutter doctor
flutter doctor 输出一片绿色勾勾的时候,我差点感动哭了。这要是放在RN时代,光是Xcode command line tools就得卡住半天。
不过很快我就遇到了第一个现实问题:公司网络限制。pub.dev(Dart的包管理仓库)经常抽风。后来发现可以用国内镜像:
# 在 ~/.zshrc 添加
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=httpshttps://storage.flutter-io.cn
解决了网络问题,我开始研究怎么提高开发效率。这时候必须吹爆两个神器:Cursor 和 Bolt.new。
Cursor + Bolt.new:我的AI外挂组合
作为重度ChatGPT依赖者,我早就习惯了写代码时开着AI助手。但普通Copilot对Flutter支持一般,直到我试了Cursor。这家伙不仅能理解整个项目上下文,还能直接生成Widget树。比如我输入“创建一个带搜索框的列表页”,它唰唰就给我生成了包含AppBar、TextField和ListView的完整代码。
更离谱的是Bolt.new。这是我从Rust社区听说的新工具,专门针对Dart/Flutter优化。它能根据自然语言描述生成符合Flutter最佳实践的代码,甚至自动处理状态管理。上周我让它“实现一个购物车页面,支持增删改数量”,结果它不仅写了UI,还集成了Provider状态管理,连空状态占位图都考虑到了。
// Bolt.new生成的购物车项Widget
class CartItem extends StatelessWidget {
final Product product;
final Function(int) onQuantityChanged;
const CartItem({Key? key, required this.product, required this.onQuantityChanged}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Image.network(product.imageUrl, width: 80),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: Theme.of(context).textTheme.titleMedium),
Text('\$${product.price}'),
// 数量选择器
Row(
children: [
IconButton(onPressed: () => onQuantityChanged(-1), icon: Icon(Icons.remove)),
Text('${product.quantity}'),
IconButton(onPressed: () => onQuantityChanged(1), icon: Icon(Icons.add)),
],
)
],
),
),
],
),
),
);
}
}
说实话,没有这些AI工具,我可能还在为StatefulWidget的生命周期头疼。现在我只需要专注业务逻辑,UI部分交给AI生成,再微调一下细节就行。省下的时间够我多刷几道LeetCode,为跳槽做准备(毕竟房贷压力摆在那里)。
第一个坑:资源管理
Flutter的资源管理和其他框架不太一样。刚开始我以为像RN那样直接require就行,结果报错:
Unable to load asset: assets/images/logo.png
查了文档才发现需要在pubspec.yaml里显式声明:
flutter:
assets:
- assets/images/
- assets/icons/
注意最后的斜杠!漏掉这个斜杠会导致整个目录不被识别。我在这个坑里卡了整整两个小时,期间还误删了项目根目录,还好有Git备份。
另一个痛点是不同分辨率的图片适配。Flutter不像Android有drawable-xxxhdpi这种机制,所有资源都放在同一个目录。解决方案是:
- 使用SVG格式(通过
flutter_svg包) - 或者提供多套图片,通过代码动态选择
我选择了方案一,因为设计师给的都是SVG源文件。不过要注意,复杂SVG在低端机上可能有性能问题,后来我们对首页图标做了光栅化处理。
平台适配:真没那么轻松
都说Flutter“一次编写,到处运行”,但实际开发中还是有不少平台差异需要处理。比如:
- iOS状态栏:默认是黑色文字,但在深色背景下看不见,需要手动设置
- Android返回键:需要重写
WillPopScope处理 - 鸿蒙系统:虽然官方说支持,但实际测试发现部分动画卡顿
最头疼的是权限处理。iOS和Android的权限申请流程完全不同,我不得不写平台特定代码:
// 权限请求工具类
import 'package:permission_handler/permission_handler.dart';
Future<bool> requestStoragePermission() async {
if (Platform.isIOS) {
// iOS需要先检查是否已授权
var status = await Permission.photos.status;
if (status.isDenied) {
return await Permission.photos.request().isGranted;
}
return status.isGranted;
} else if (Platform.isAndroid) {
// Android需要动态申请
var status = await Permission.storage.request();
return status.isGranted;
}
return true;
}
这里用到了permission_handler这个第三方包,强烈推荐。比自己写Method Channel简单多了。
性能优化:别让UI卡成PPT
上线前我们做了性能测试,发现列表滚动时偶尔掉帧。用Flutter DevTools分析后,发现是每个列表项都重新构建了整个Widget树。解决方案是:
- 使用const构造函数:对不变的Widget加const
- 拆分StatefulWidget:把可变部分和不可变部分分离
- 避免在build方法里做计算:提前计算好数据
// 优化前
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
// 每次都重新创建Product对象
return ProductItem(product: Product.fromJson(products[index]));
},
);
}
// 优化后
final _cachedProducts = products.map((json) => Product.fromJson(json)).toList();
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _cachedProducts.length,
itemBuilder: (context, index) {
// 直接使用缓存对象
return const ProductItem(product: _cachedProducts[index]);
},
);
}
另外,图片加载一定要用cached_network_image包,否则快速滑动列表时会疯狂请求网络,用户体验极差。
发布上线:应用商店的血泪史
终于到了发布环节。iOS还算顺利,但Google Play给了我们一个下马威——要求targetSdkVersion必须是33以上。我们的Flutter版本有点旧,升级后发现几个插件不兼容。
最崩溃的是鸿蒙应用市场。虽然Flutter官方声称支持,但审核时被拒了三次,理由是“应用存在兼容性问题”。后来发现是用了某个Android特有的API,通过条件编译解决:
if (defaultTargetPlatform == TargetPlatform.android) {
// Android特有代码
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
// iOS特有代码
} else {
// 其他平台通用处理
}
经过两周的反复修改,App终于在三个平台都上线了。用户反馈还不错,特别是动画流畅度比之前的RN版本好很多。最让我欣慰的是,包体积比预期小——release版本只有15MB左右,而RN版本通常要25MB+。
这些坑我替你踩过了
回顾这一个月的Flutter之旅,有几个关键经验想分享:
| 问题类型 | 坑点 | 解决方案 |
|---|---|---|
| 环境配置 | pub.dev访问慢 | 配置国内镜像 |
| 资源管理 | assets路径错误 | 注意pubspec.yaml中的斜杠 |
| 性能问题 | 列表卡顿 | 使用const、缓存对象、图片缓存 |
| 平台差异 | 权限处理 | 用permission_handler包 |
| 发布问题 | 应用商店审核 | 提前了解各平台要求 |
写在最后
现在回头看,选择Flutter是对的。虽然学习曲线有点陡,但开发效率确实高。上周产品又提了新需求,我居然敢直接说“两周内搞定”,这在RN时代是不敢想的。
不过我也清醒地知道,Flutter不是银弹。对于需要深度原生集成的场景,还是得写Platform Channel。但对于我们这种小团队、快速迭代的业务,Flutter简直是救命稻草。
房贷压力依然在,但至少现在有了更多选择。说不定哪天我就带着Flutter技能跳槽涨薪了呢?在此之前,先去改产品刚提的紧急bug吧——他说用户反馈点击按钮没反应,八成又是我忘了加onPressed回调...
(完)
后记:这篇文章写于周日凌晨三点,刚修完一个线上bug。如果你也在北漂还贷,不妨试试Flutter。至少,它能让你早点下班,多陪陪家人——或者,多刷几道算法题准备跳槽。

评论 0