Dart: Dart 与 Flutter 协作 — 共享逻辑与平台通信
Dart 是 Flutter 的心脏 — 同一套语言,驱动 UI、逻辑、原生通信。
1. 你将学到
- Dart 层在 Flutter 架构中的定位
- 共享业务逻辑包的设计(pure Dart package)
- MethodChannel:Dart ↔ Native 通信
- FFI:Dart 调用 C 库
- Bob 场景:DataPipeline 核心逻辑同时服务 CLI 与 Flutter 仪表盘
2. 一个开发者的真实故事
(1) 痛点:CLI 和 Flutter App 各写一套业务逻辑
Bob 的 DataPipeline 有两个前端:CLI 工具(供开发者使用)和 Flutter 仪表盘(供 SaaS 客户查看报表)。两套系统各自实现数据处理逻辑,导致:修一个税额计算 bug 要改 2 处,一次漏改导致 CLI 报表正确但仪表盘错误,客户投诉。
(2) 共享逻辑包的解法
将核心业务逻辑抽取为 pure Dart package,CLI 和 Flutter 都依赖它。修 bug 只需改一处。
graph TD A[DataPipeline Core<br/>pure Dart] --> B[CLI App] A --> C[Flutter Dashboard] C --> D[MethodChannel] D --> E[iOS Native] D --> F[Android Native] C --> G[FFI] G --> H[C Library]
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- 核心逻辑只维护一份,CLI 和 Flutter 自动同步
- pure Dart package 可在所有平台运行(VM/Web/Native)
- MethodChannel 让 Flutter 访问原生能力(传感器、推送等)
3. Dart 在 Flutter 架构中的定位
(1) 架构分层
| 层级 | 技术 | 职责 |
|---|---|---|
| UI 层 | Flutter Widget | 界面渲染与交互 |
| 逻辑层 | Dart (pure) | 业务逻辑、数据处理 |
| 平台层 | Native (Kotlin/Swift) | 平台特定功能 |
| 通信层 | MethodChannel / FFI | Dart ↔ Native 桥梁 |
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Flutter 项目中的 Dart 逻辑
// This code runs in BOTH CLI and Flutter environments
// lib/data_pipeline_core.dart
class OrderAnalyzer {
final double taxRate;
OrderAnalyzer({this.taxRate = 0.08});
double calculateTax(double amount) => amount * taxRate;
double calculateTotal(double amount) => amount * (1 + taxRate);
Map<String, double> groupByCategory(List``<Order>`` orders) {
final result = <String, double>{};
for (final order in orders) {
result.update(order.category, (v) => v + order.amount, ifAbsent: () => order.amount);
}
return result;
}
}
class Order {
final String id;
final double amount;
final String category;
Order({required this.id, required this.amount, required this.category});
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
4. 共享业务逻辑包设计
(1) Pure Dart Package 结构
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:共享包结构
packages/
data_pipeline_core/
lib/
src/
models/
order.dart
product.dart
customer.dart
services/
analyzer.dart
aggregator.dart
transformer.dart
utils/
formatters.dart
validators.dart
data_pipeline_core.dart # Barrel export
test/
analyzer_test.dart
aggregator_test.dart
pubspec.yaml
```text
```text
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Barrel export
// lib/data_pipeline_core.dart
// Barrel file - exports all public APIs
// Models
export 'src/models/order.dart';
export 'src/models/product.dart';
export 'src/models/customer.dart';
// Services
export 'src/services/analyzer.dart';
export 'src/services/aggregator.dart';
export 'src/services/transformer.dart';
// Utils
export 'src/utils/formatters.dart';
export 'src/utils/validators.dart';
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:pubspec.yaml for pure Dart package
name: data_pipeline_core
description: Core business logic for DataPipeline - pure Dart, no Flutter dependency
version: 1.0.0
environment:
sdk: ^3.0.0
dependencies:
json_annotation: ^4.8.0
dev_dependencies:
test: ^1.24.0
json_serializable: ^6.7.0
build_runner: ^2.4.0
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:两个消费者
# CLI app pubspec.yaml
dependencies:
data_pipeline_core:
path: ../packages/data_pipeline_core
args: ^2.4.2
# Flutter app pubspec.yaml
dependencies:
data_pipeline_core:
path: ../packages/data_pipeline_core
flutter:
sdk: flutter
| 原则 | 说明 |
|---|---|
| 无 Flutter 依赖 | pubspec.yaml 不依赖 flutter |
| 无 dart:io 直接使用 | 用抽象接口替代文件/网络操作 |
| 无 UI 代码 | 纯数据处理逻辑 |
| 完整测试 | 不需要 Flutter 运行时即可测试 |
5. MethodChannel
(1) Dart ↔ Native 通信
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:MethodChannel 调用原生
import 'package:flutter/services.dart';
class NativeService {
static const _channel = MethodChannel('com.datapipeline/native');
// Call native method
Future``<String>`` getDeviceId() async {
try {
final deviceId = await _channel.invokeMethod``<String>``('getDeviceId');
return deviceId ?? 'unknown';
} on PlatformException catch (e) {
print('Failed to get device ID: ${e.message}');
return 'error';
}
}
// Call with arguments
Future``<bool>`` saveToFile(String path, String content) async {
try {
final result = await _channel.invokeMethod``<bool>``('saveToFile', {
'path': path,
'content': content,
});
return result ?? false;
} on PlatformException catch (e) {
print('Failed to save: ${e.message}');
return false;
}
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(2) Native 端实现
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Android (Kotlin) 端
// Android implementation
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
"com.datapipeline/native").setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceId" -> {
val deviceId = Settings.Secure.getString(
contentResolver, Settings.Secure.ANDROID_ID)
result.success(deviceId)
}
"saveToFile" -> {
val path = call.argument<String>("path")
val content = call.argument<String>("content")
// Save file logic
result.success(true)
}
else -> result.notImplemented()
}
}
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 通信方式 | 方向 | 数据类型 | 适用场景 |
|---|---|---|---|
| MethodChannel | Dart → Native | 标准类型 | 调用原生方法 |
| EventChannel | Native → Dart | 流 | 原生事件流 |
| BasicMessageChannel | 双向 | 字符串/字节 | 双向消息 |
6. FFI — 调用 C 库
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:dart:ffi 调用 C 函数
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// C function signature: double process_data(double* data, int length)
typedef ProcessDataNative = Double Function(Pointer``<Double>``, Int32);
typedef ProcessDataDart = double Function(Pointer``<Double>``, int);
void main() {
// Load dynamic library
final dylib = DynamicLibrary.open('libdatapipeline.so');
// Look up function
final processData = dylib.lookupFunction<ProcessDataNative, ProcessDataDart>('process_data');
// Prepare data
final dataPtr = calloc``<Double>``(5);
for (var i = 0; i < 5; i++) {
dataPtr[i] = (i + 1) * 100.0;
}
// Call C function
final result = processData(dataPtr, 5);
print('Result from C: $result');
// Free memory
calloc.free(dataPtr);
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| FFI 概念 | 说明 |
|---|---|
DynamicLibrary |
加载动态链接库 |
lookupFunction |
查找 C 函数 |
| `Pointer`` |
指向原生内存 |
calloc / free |
分配/释放原生内存 |
NativeType |
C 类型映射 |
7. Bob 场景:DataPipeline 双前端架构
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:共享核心 + 双前端
// packages/data_pipeline_core/lib/src/services/analyzer.dart
class OrderAnalyzer {
final double taxRate;
OrderAnalyzer({this.taxRate = 0.08});
AnalysisResult analyze(List``<Order>`` orders) {
final completed = orders.where((o) => o.status == 'completed').toList();
final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
final tax = revenue * taxRate;
final byCategory = <String, double>{};
for (final o in completed) {
byCategory.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
}
return AnalysisResult(
totalOrders: orders.length,
completedOrders: completed.length,
revenue: revenue,
tax: tax,
revenueByCategory: byCategory,
);
}
}
class AnalysisResult {
final int totalOrders;
final int completedOrders;
final double revenue;
final double tax;
final Map<String, double> revenueByCategory;
AnalysisResult({
required this.totalOrders,
required this.completedOrders,
required this.revenue,
required this.tax,
required this.revenueByCategory,
});
double get totalWithTax => revenue + tax;
double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}
class Order {
final String id;
final double amount;
final String status;
final String category;
Order({required this.id, required this.amount, required this.status, required this.category});
}
// CLI consumer
void main() {
final analyzer = OrderAnalyzer(taxRate: 0.08);
final orders = [
Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
];
final result = analyzer.analyze(orders);
print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
}
// Flutter consumer would use the same OrderAnalyzer
// but render results in a Widget instead of print()
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
8. 完整示例:DataPipeline 共享架构
// ============================================
// DataPipeline Shared Architecture
// Pure Dart core + CLI consumer + Flutter consumer
// ============================================
// ---- Core Library (pure Dart) ----
// lib/src/models/order.dart
class Order {
final String id;
final double amount;
final String status;
final String category;
final String region;
const Order({
required this.id,
required this.amount,
required this.status,
required this.category,
this.region = 'US',
});
double get tax => amount * _taxRate(region);
double get total => amount + tax;
String get formatAmount => '\$${amount.toStringAsFixed(2)} USD';
static double _taxRate(String region) => switch (region) {
'US' => 0.08,
'EU' => 0.20,
'UK' => 0.15,
_ => 0.10,
};
}
// lib/src/services/analyzer.dart
class OrderAnalyzer {
const OrderAnalyzer();
AnalysisResult analyze(List``<Order>`` orders) {
final completed = orders.where((o) => o.status == 'completed').toList();
final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
final tax = completed.fold``<double>``(0, (s, o) => s + o.tax);
final total = revenue + tax;
final byCategory = <String, CategoryResult>{};
for (final o in completed) {
byCategory.update(
o.category,
(v) => v.addOrder(o),
ifAbsent: () => CategoryResult.fromOrder(o),
);
}
return AnalysisResult(
totalOrders: orders.length,
completedOrders: completed.length,
revenue: revenue,
tax: tax,
total: total,
byCategory: byCategory,
);
}
}
class CategoryResult {
final int count;
final double revenue;
const CategoryResult({required this.count, required this.revenue});
factory CategoryResult.fromOrder(Order order) =>
CategoryResult(count: 1, revenue: order.amount);
CategoryResult addOrder(Order order) =>
CategoryResult(count: count + 1, revenue: revenue + order.amount);
double get average => count > 0 ? revenue / count : 0;
}
class AnalysisResult {
final int totalOrders;
final int completedOrders;
final double revenue;
final double tax;
final double total;
final Map<String, CategoryResult> byCategory;
const AnalysisResult({
required this.totalOrders,
required this.completedOrders,
required this.revenue,
required this.tax,
required this.total,
required this.byCategory,
});
double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}
// ---- CLI Consumer ----
void main() {
final analyzer = OrderAnalyzer();
final orders = [
Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'),
Order(id: 'ORD-004', amount: 2100.0, status: 'completed', category: 'Electronics', region: 'EU'),
];
final result = analyzer.analyze(orders);
print('=== DataPipeline Analytics ===');
print('Orders: ${result.completedOrders}/${result.totalOrders}');
print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
print('Total: \$${result.total.toStringAsFixed(2)} USD');
print('Average: \$${result.averageOrderValue.toStringAsFixed(2)} USD');
print('\nBy Category:');
final sorted = result.byCategory.entries.toList()
..sort((a, b) => b.value.revenue.compareTo(a.value.revenue));
for (final entry in sorted) {
final cat = entry.value;
print(' ${entry.key}: ${cat.count} orders, '
'\$${cat.revenue.toStringAsFixed(2)} USD (avg: \$${cat.average.toStringAsFixed(2)})');
}
print('\n--- Platform Compatibility ---');
print('Core logic: Pure Dart (CLI + Flutter + Web)');
print('CLI: dart:io + args package');
print('Flutter: Widgets + MethodChannel + FFI');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
=== DataPipeline Analytics ===
Orders: 3/4
Revenue: $4490.00 USD
Tax: $798.00 USD
Total: $5288.00 USD
Average: $1496.67 USD
By Category:
Electronics: 2 orders, $3600.00 USD (avg: $1800.00)
Clothing: 1 orders, $890.00 USD (avg: $890.00)
--- Platform Compatibility ---
Core logic: Pure Dart (CLI + Flutter + Web)
CLI: dart:io + args package
Flutter: Widgets + MethodChannel + FFI
❓ 常见问题
Q:pure Dart package 能在 Flutter Web 上运行吗? A:可以,只要不使用 dart:io。Web 平台不支持 dart:io,使用抽象接口(如 http 包的 Client)替代直接文件/网络操作。
Q:MethodChannel 的数据传递有大小限制吗? A:没有硬性限制,但大对象需要序列化/反序列化,性能不佳。大数据建议用 FFI 或临时文件传递。
Q:FFI 支持 iOS 吗? A:支持。iOS 上加载 .dylib 或 .framework。Flutter 3.0+ 的 FFI 支持所有主要平台。
Q:什么时候用 MethodChannel,什么时候用 FFI? A:MethodChannel 适合调用平台 API(传感器、推送、文件保存);FFI 适合调用 C 库(图像处理、加密、数据库引擎)。
Q:共享包的测试需要在 Flutter 环境吗? A:不需要。pure Dart package 用
dart test即可测试,不依赖 Flutter。这是抽取共享包的重要好处。
Q:如何确保共享包不意外引入 Flutter 依赖? A:pubspec.yaml 不依赖 flutter,不 import flutter 的包。CI 中用
dart test(不是flutter test)运行测试来验证。
Q:DataPipeline 核心包应该用 monorepo 还是独立仓库? A:推荐 monorepo(Melos 或 path 依赖),方便同步修改核心包和消费者。独立仓库适合开源发布。
📖 小节
- Dart 是 Flutter 的核心语言,UI 层和逻辑层都由 Dart 驱动
- 共享逻辑包(pure Dart)让 CLI 和 Flutter 复用同一份业务代码
- MethodChannel 实现 Dart ↔ Native 通信,用于平台特定功能
- FFI 直接调用 C 库,适合高性能计算和现有 C 代码集成
- 架构原则:核心逻辑 pure Dart,UI 层按平台定制
📝 作业
- 基础题(难度⭐):创建一个 pure Dart package(
dart create -t package-simple),包含一个formatUSD函数,分别在 CLI 和 Flutter 项目中引用并调用它。 - 进阶题(难度⭐⭐):设计 DataPipeline 的核心包结构:3 个模型类 + 2 个服务类 + 1 个 barrel export。确保 pubspec.yaml 无 Flutter 依赖,编写
dart test可运行的测试。 - 挑战题(难度⭐⭐⭐):实现一个抽象的
DataStorage接口(pure Dart),分别用 dart:io(CLI)和 SharedPreferences(Flutter)实现。核心包只依赖接口,具体实现由消费者注入。