Flutter初体验:用一套代码搞定三端,北漂码农的自救指南

测试环境炸了
2026-03-09 02:27
阅读 1753

上周五晚上十点半,我瘫在工位上盯着屏幕发呆。产品经理刚甩过来一个新需求:“咱们得有个独立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

解决了网络问题,我开始研究怎么提高开发效率。这时候必须吹爆两个神器:CursorBolt.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这种机制,所有资源都放在同一个目录。解决方案是:

  1. 使用SVG格式(通过flutter_svg包)
  2. 或者提供多套图片,通过代码动态选择

我选择了方案一,因为设计师给的都是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树。解决方案是:

  1. 使用const构造函数:对不变的Widget加const
  2. 拆分StatefulWidget:把可变部分和不可变部分分离
  3. 避免在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

最热最新
暂无评论
测试环境炸了Lv.1
0
影响力
0
文章
0
粉丝