像Google那样编写Flutter代码

Flutter 中有无数的编码约定在流传。从流行的"Flutter Clean Architecture"到社区驱动的风格指南,开发者们多年来一直在遵循外部约定。我就是其中之一。

在过去的五年里,我遵循社区的最佳实践,强制执行严格的 Lint 规则,并根据流行的 Medium 文章和 YouTube 教程来构建我的 Flutter 应用。我的代码是"干净的",我的架构是"规范的"。

但当我开始研究 Google Flutter 团队实际是如何编写 Flutter 代码时,一切都改变了。

这个发现令人震惊:Google Flutter 团队遵循的模式与流行的社区约定完全矛盾

在这篇文章中,我将分享我通过分析 50 多个 Flutter 框架文件、Google 自己的 Flutter 应用以及 Flutter 核心团队成员的贡献所发现的内部编码模式。

Widget 组合优于复杂继承

社区方式:

dart 复制代码
scala 复制代码
abstract class BaseWidget extends StatelessWidget {
  final String title;
  final EdgeInsets padding;

  const BaseWidget({
    required this.title,
    this.padding = EdgeInsets.zero,
  });

  @override
  Widget build(BuildContext context) {
    return buildContent(context);
  }

  Widget buildContent(BuildContext context);
}

class MyWidget extends BaseWidget {
  const MyWidget({required super.title});

  @override
  Widget buildContent(BuildContext context) {
    return Container(
      padding: padding,
      child: Text(title),
    );
  }
}

Google方式:

dart 复制代码
class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.title,
  });

  final String title;

  @override
  Widget build(BuildContext context) {
    return _buildContent(context);
  }

  Widget _buildContent(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16.0),
      child: Text(title),
    );
  }
}

Google 的 Flutter 团队避免复杂的继承层级结构,转而青睐组合。他们更喜欢小巧、专注、只做好一件事的 Widget,而不是创建试图解决多个问题的复杂基类。

真实世界示例:

dart 复制代码
// 不创建 BaseCardWidget
class ProductCard extends StatelessWidget {
  const ProductCard({
    super.key,
    required this.product,
  });

  final Product product;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          _buildImage(),
          _buildTitle(),
          _buildPrice(),
        ],
      ),
    );
  }

  Widget _buildImage() => Image.network(product.imageUrl);
  Widget _buildTitle() => Text(product.name);
  Widget _buildPrice() => Text('$${product.price}');
}

状态管理:保持简单,傻瓜式(KISS 原则)

社区痴迷:

dart 复制代码
// 每个人都这么干
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitial()) {
    on<CounterIncrement>(_onIncrement);
    on<CounterDecrement>(_onDecrement);
  }

  void _onIncrement(CounterIncrement event, Emitter<CounterState> emit) {
    emit(CounterValue(state.value + 1));
  }

  void _onDecrement(CounterDecrement event, Emitter<CounterState> emit) {
    emit(CounterValue(state.value - 1));
  }
}

Google 的现实:

dart 复制代码
// Google 的 Flutter 团队这么做
class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$_counter'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

在分析了 Google 的 Flutter 代码库后,我发现 Flutter 团队使用 StatefulWidgetsetState 的频率远高于任何复杂的状态管理解决方案。他们将高级状态管理方案保留给真正复杂的场景,而不是简单的 UI 状态。

原则: 使用最简单的解决方案。不要过度设计。

方法命名:面向动作,而非面向描述

社区模式:

dart 复制代码
class UserService {
  Future<User> getUserById(String id) async {}
  Future<void> updateUserProfile(User user) async {}
  Future<void> deleteUserAccount(String id) async {}
}

Google模式:

dart 复制代码
class UserService {
  Future<User> fetch(String id) async {}
  Future<void> update(User user) async {}
  Future<void> delete(String id) async {}
}

Google 的 Flutter 团队更喜欢简洁、面向动作 的方法命名,在上下文中意义清晰。上下文(UserService,用户服务)已经告诉你这是关于用户的,所以方法名会专注于动作fetch(获取)、update(更新)、delete(删除)。然而,他们不会为了简洁而牺牲清晰度------当可能出现歧义时,他们仍然会使用描述性名称,例如 fetchUserById()getAuthToken()

真实世界示例:

dart 复制代码
class CartService {
  Future<void> add(Product product) async {}
  Future<void> remove(String productId) async {}
  Future<void> clear() async {}
  Future<double> total() async {}
}

Widget 文件结构:一个 Widget,一个目的**

社区结构:

dart 复制代码
// user_widgets.dart
class UserCard extends StatelessWidget {}
class UserList extends StatelessWidget {}
class UserProfile extends StatelessWidget {}
class UserSettings extends StatelessWidget {}

Google结构:

scala 复制代码
// user_card.dart
class UserCard extends StatelessWidget {}

// user_list.dart  
class UserList extends StatelessWidget {}

// user_profile.dart
class UserProfile extends StatelessWidget {}

// user_settings.dart
class UserSettings extends StatelessWidget {}

Google 的 Flutter 团队遵循严格的一个 Widget 一个文件 的规则。每个 Widget 都存在于自己的文件中。

这遵循了单一职责原则:一个文件,一个 Widget,一个目的。

它存在于自己的文件中,使得代码库更易于维护和导航。

常量:上下文驱动分组

社区方式:

dart 复制代码
// constants.dart
class AppConstants {
  static const double defaultPadding = 16.0;
  static const double defaultRadius = 8.0;
  static const Color primaryColor = Colors.blue;
  static const String appName = 'MyApp';
  static const int maxRetries = 3;
}

Google方式:

dart 复制代码
// ui_constants.dart
class UiConstants {
  static const double defaultPadding = 16.0;
  static const double defaultRadius = 8.0;
}

// theme_constants.dart
class ThemeConstants {
  static const Color primaryColor = Colors.blue;
  static const Color secondaryColor = Colors.grey;
}

// network_constants.dart
class NetworkConstants {
  static const int maxRetries = 3;
  static const Duration timeout = Duration(seconds: 30);
}

Google 团队根据上下文对常量进行分组,而不是将所有常量都放在一个巨大的文件中。这使得查找和维护相关的常量变得更加容易。

错误处理:明确且即时

社区模式:

dart 复制代码
class ApiService {
  Future<Result<User>> getUser(String id) async {
    try {
      final user = await _fetchUser(id);
      return Success(user);
    } catch (e) {
      return Failure(e.toString());
    }
  }
}

Google模式:

dart 复制代码
class ApiService {
  Future<User> getUser(String id) async {
    final response = await _httpClient.get('/users/$id');

    if (response.statusCode != 200) {
      throw ApiException('Failed to fetch user: ${response.statusCode}');
    }

    return User.fromJson(response.data);
  }
}

Google 的 Flutter 团队更倾向于使用异常(exceptions)进行明确的错误处理,而不是将所有内容都包装在 Result 类型中。他们让错误自然地冒泡,并在适当的级别进行处理。

Widget 测试:行为重于实现

社区关注点:

dart 复制代码
testWidgets('Counter increments smoke test', (tester) async {
await tester.pumpWidget(const MyApp());

// Verify initial state
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);

// Tap increment button
await tester.tap(find.byIcon(Icons.add));
await tester.pump();

// Verify state changed
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});

Google关注点

dart 复制代码
testWidgets('User can increment counter', (tester) async {
await tester.pumpWidget(const MyApp());

// User sees initial counter
expect(find.text('0'), findsOneWidget);

// User taps increment
await tester.tap(find.byIcon(Icons.add));
await tester.pump();

// User sees updated counter
expect(find.text('1'), findsOneWidget);
});

Google 的应用级 Widget 测试侧重于用户行为而非实现细节。虽然框架内部仍然需要实现层面的测试(如渲染对象或布局行为),但他们来自 Flutter Gallery 等项目的应用测试更强调用户体验而非代码结构。

结论

在研究了 Google 的 Flutter 模式后,我意识到许多开发者(包括我自己)一直在将 Flutter 开发复杂化。这并不是说社区模式是错误的------它们对于大型团队、受监管行业或企业应用来说很有价值。但 Google 的方法令人耳目一新地简单,因为他们的团队优先考虑简洁性,并且知道何时进行优化。

关键模式:

  • 保持 Widget 小巧且专注
  • 使用最简单的状态管理方案
  • 命名清晰简洁
  • 一个 Widget 一个文件
  • 根据上下文对常量进行分组
  • 明确处理错误
  • 在应用级测试中测试用户行为

这些模式改变了我编写 Flutter 代码的方式。我的应用程序更易于维护,我的团队效率更高,并且调试变得显著更容易。

洞察:

社区约定并非错误,它们只是对于许多应用程序而言,比实际所需的更"重"。Google 的内部团队之所以能够保持简单,是因为他们拥有深厚的框架知识,并且知道何时真正需要引入复杂性。

最后请关注我的公众号:OpenFlutter

相关推荐
C澒4 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅5 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘7 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端