像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

相关推荐
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60617 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角8 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!8 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi10 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马12 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒12 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端