flutter-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter Development
Flutter开发
Build beautiful, natively compiled applications for mobile, web, desktop, and embedded from a single codebase.
通过单一代码库构建美观的原生编译应用,支持移动、Web、桌面及嵌入式设备。
Supported Platforms
支持的平台
| Platform | Status | Notes |
|---|---|---|
| iOS | Stable | Full native performance |
| Android | Stable | Full native performance |
| Web | Stable | PWA support |
| macOS | Stable | Native desktop |
| Windows | Stable | Native desktop |
| Linux | Stable | Native desktop |
| 平台 | 状态 | 说明 |
|---|---|---|
| iOS | 稳定版 | 完整原生性能 |
| Android | 稳定版 | 完整原生性能 |
| Web | 稳定版 | 支持PWA |
| macOS | 稳定版 | 原生桌面端 |
| Windows | 稳定版 | 原生桌面端 |
| Linux | 稳定版 | 原生桌面端 |
Project Structure
项目结构
my_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── features/
│ │ ├── home/
│ │ │ ├── home_screen.dart
│ │ │ ├── home_controller.dart
│ │ │ └── widgets/
│ │ └── settings/
│ ├── core/
│ │ ├── theme/
│ │ ├── utils/
│ │ └── constants/
│ └── shared/
│ ├── models/
│ ├── services/
│ └── widgets/
├── test/
├── pubspec.yaml
└── analysis_options.yamlmy_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── features/
│ │ ├── home/
│ │ │ ├── home_screen.dart
│ │ │ ├── home_controller.dart
│ │ │ └── widgets/
│ │ └── settings/
│ ├── core/
│ │ ├── theme/
│ │ ├── utils/
│ │ └── constants/
│ └── shared/
│ ├── models/
│ ├── services/
│ └── widgets/
├── test/
├── pubspec.yaml
└── analysis_options.yamlBasic Structure
基础结构
Main Entry
主入口
dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}Stateless Widget
无状态组件(Stateless Widget)
dart
class GreetingCard extends StatelessWidget {
const GreetingCard({
super.key,
required this.name,
});
final String name;
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Hello, $name!',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}dart
class GreetingCard extends StatelessWidget {
const GreetingCard({
super.key,
required this.name,
});
final String name;
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Hello, $name!',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}Stateful Widget
有状态组件(Stateful Widget)
dart
class Counter extends StatefulWidget {
const Counter({super.key});
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count: $_count',
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}dart
class Counter extends StatefulWidget {
const Counter({super.key});
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count: $_count',
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}State Management
状态管理
Riverpod (Recommended)
Riverpod(推荐)
dart
// Provider definition
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// Usage in widget
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
// Async provider
final itemsProvider = FutureProvider<List<Item>>((ref) async {
final repository = ref.watch(repositoryProvider);
return repository.fetchItems();
});dart
// Provider定义
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// 在组件中使用
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
// 异步Provider
final itemsProvider = FutureProvider<List<Item>>((ref) async {
final repository = ref.watch(repositoryProvider);
return repository.fetchItems();
});BLoC Pattern
BLoC模式
dart
// Events
abstract class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}
// State
class CounterState {
final int count;
const CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(0)) {
on<IncrementPressed>((event, emit) {
emit(CounterState(state.count + 1));
});
on<DecrementPressed>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
// Usage
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
)dart
// 事件
abstract class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}
// 状态
class CounterState {
final int count;
const CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(0)) {
on<IncrementPressed>((event, emit) {
emit(CounterState(state.count + 1));
});
on<DecrementPressed>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
// 使用方式
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
)Navigation
导航
GoRouter (Recommended)
GoRouter(推荐)
dart
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailsScreen(id: id);
},
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
);
// Usage
MaterialApp.router(
routerConfig: router,
)
// Navigate
context.go('/details/123');
context.push('/settings');
context.pop();dart
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailsScreen(id: id);
},
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
);
// 使用方式
MaterialApp.router(
routerConfig: router,
)
// 导航操作
context.go('/details/123');
context.push('/settings');
context.pop();Common Widgets
常用组件
Lists
列表
dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(item.imageUrl),
),
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push('/details/${item.id}'),
);
},
)dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(item.imageUrl),
),
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push('/details/${item.id}'),
);
},
)Forms
表单
dart
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
// Process login
}
}
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: const Text('Login'),
),
],
),
);
}
}dart
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
// 处理登录逻辑
}
}
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '邮箱',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.contains('@')) {
return '请输入有效的邮箱地址';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: '密码',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) {
return '密码长度至少为6位';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: const Text('登录'),
),
],
),
);
}
}Networking
网络请求
Dio HTTP Client
Dio HTTP客户端
dart
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
)) {
_dio.interceptors.add(LogInterceptor());
}
Future<List<Item>> getItems() async {
try {
final response = await _dio.get('/items');
return (response.data as List)
.map((json) => Item.fromJson(json))
.toList();
} on DioException catch (e) {
throw ApiException(e.message ?? 'Unknown error');
}
}
}dart
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
)) {
_dio.interceptors.add(LogInterceptor());
}
Future<List<Item>> getItems() async {
try {
final response = await _dio.get('/items');
return (response.data as List)
.map((json) => Item.fromJson(json))
.toList();
} on DioException catch (e) {
throw ApiException(e.message ?? '未知错误');
}
}
}Local Storage
本地存储
Hive (Recommended)
Hive(推荐)
dart
// Model
(typeId: 0)
class Item extends HiveObject {
(0)
late String id;
(1)
late String name;
}
// Setup
await Hive.initFlutter();
Hive.registerAdapter(ItemAdapter());
await Hive.openBox<Item>('items');
// Usage
final box = Hive.box<Item>('items');
await box.put('key', item);
final item = box.get('key');dart
// 模型
(typeId: 0)
class Item extends HiveObject {
(0)
late String id;
(1)
late String name;
}
// 初始化
await Hive.initFlutter();
Hive.registerAdapter(ItemAdapter());
await Hive.openBox<Item>('items');
// 使用方式
final box = Hive.box<Item>('items');
await box.put('key', item);
final item = box.get('key');SharedPreferences
SharedPreferences
dart
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
final token = prefs.getString('token');dart
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
final token = prefs.getString('token');Platform-Specific Code
平台特定代码
dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
Widget build(BuildContext context) {
if (kIsWeb) {
return WebLayout();
} else if (Platform.isIOS) {
return CupertinoLayout();
} else if (Platform.isAndroid) {
return MaterialLayout();
} else if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
return DesktopLayout();
}
return DefaultLayout();
}dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
Widget build(BuildContext context) {
if (kIsWeb) {
return WebLayout();
} else if (Platform.isIOS) {
return CupertinoLayout();
} else if (Platform.isAndroid) {
return MaterialLayout();
} else if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
return DesktopLayout();
}
return DefaultLayout();
}Testing
测试
Widget Tests
组件测试
dart
testWidgets('Counter increments', (tester) async {
await tester.pumpWidget(const MaterialApp(home: Counter()));
expect(find.text('Count: 0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('Count: 1'), findsOneWidget);
});dart
testWidgets('计数器递增功能', (tester) async {
await tester.pumpWidget(const MaterialApp(home: Counter()));
expect(find.text('Count: 0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('Count: 1'), findsOneWidget);
});Unit Tests
单元测试
dart
test('Item fromJson parses correctly', () {
final json = {'id': '1', 'name': 'Test'};
final item = Item.fromJson(json);
expect(item.id, '1');
expect(item.name, 'Test');
});dart
test('Item.fromJson解析正确', () {
final json = {'id': '1', 'name': 'Test'};
final item = Item.fromJson(json);
expect(item.id, '1');
expect(item.name, 'Test');
});Performance
性能优化
Best Practices
最佳实践
dart
// Use const constructors
const SizedBox(height: 16)
// Avoid rebuilds with const
const MyStaticWidget()
// Use ListView.builder for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)
// Cache images
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => const CircularProgressIndicator(),
)dart
// 使用const构造函数
const SizedBox(height: 16)
// 避免不必要的重建,使用const
const MyStaticWidget()
// 长列表使用ListView.builder
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)
// 图片缓存
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => const CircularProgressIndicator(),
)Best Practices
最佳实践
DO:
建议:
- Use const constructors everywhere possible
- Split widgets into smaller components
- Use Riverpod or BLoC for state
- Follow Flutter naming conventions
- Write widget tests
- 尽可能在所有地方使用const构造函数
- 将组件拆分为更小的子组件
- 使用Riverpod或BLoC进行状态管理
- 遵循Flutter命名规范
- 编写组件测试
DON'T:
避免:
- Put logic in build methods
- Create god widgets
- Use setState for complex state
- Ignore null safety
- Skip code generation setup
- 在build方法中编写业务逻辑
- 创建过于庞大的"上帝组件"
- 使用setState处理复杂状态
- 忽略空安全
- 跳过代码生成配置
Dart 3 Features
Dart 3新特性
Sealed Classes and Pattern Matching
密封类与模式匹配
dart
// Sealed classes for exhaustive pattern matching
sealed class Result<T> {}
class Success<T> extends Result<T> { final T value; Success(this.value); }
class Failure<T> extends Result<T> { final String error; Failure(this.error); }
// Exhaustive switch
String display(Result<User> result) => switch (result) {
Success(value: final user) => 'Hello, ${user.name}',
Failure(error: final msg) => 'Error: $msg',
};dart
// 密封类用于穷尽式模式匹配
sealed class Result<T> {}
class Success<T> extends Result<T> { final T value; Success(this.value); }
class Failure<T> extends Result<T> { final String error; Failure(this.error); }
// 穷尽式switch
String display(Result<User> result) => switch (result) {
Success(value: final user) => 'Hello, ${user.name}',
Failure(error: final msg) => 'Error: $msg',
};Records
记录类型(Records)
dart
// Lightweight data tuples
(String, int) getUserInfo() => ('John', 30);
// Named fields
({String name, int age}) getUserDetails() => (name: 'John', age: 30);
// Destructuring
final (name, age) = getUserInfo();
final (:name, :age) = getUserDetails();dart
// 轻量级数据元组
(String, int) getUserInfo() => ('John', 30);
// 命名字段
({String name, int age}) getUserDetails() => (name: 'John', age: 30);
// 解构
final (name, age) = getUserInfo();
final (:name, :age) = getUserDetails();Extension Types
扩展类型(Extension Types)
dart
// Zero-cost type wrappers (compile-time only)
extension type UserId(String value) {
bool get isValid => value.isNotEmpty;
}
extension type Email(String value) {
factory Email.validated(String raw) {
if (!raw.contains('@')) throw FormatException('Invalid email');
return Email(raw);
}
}
// Usage - type-safe, zero runtime cost
void sendEmail(Email to, UserId from) { ... }
sendEmail(Email.validated('a@b.com'), UserId('user123'));dart
// 零成本类型包装(仅编译时有效)
extension type UserId(String value) {
bool get isValid => value.isNotEmpty;
}
extension type Email(String value) {
factory Email.validated(String raw) {
if (!raw.contains('@')) throw FormatException('Invalid email');
return Email(raw);
}
}
// 使用方式 - 类型安全,运行时零开销
void sendEmail(Email to, UserId from) { ... }
sendEmail(Email.validated('a@b.com'), UserId('user123'));If-Case and Switch Expressions
If-Case与Switch表达式
dart
// If-case for pattern matching
if (json case {'name': String name, 'age': int age}) {
print('$name is $age years old');
}
// Switch expressions (concise)
final icon = switch (status) {
'active' => Icons.check_circle,
'pending' => Icons.hourglass_empty,
'error' => Icons.error,
_ => Icons.help,
};dart
// If-case模式匹配
if (json case {'name': String name, 'age': int age}) {
print('$name is $age years old');
}
// Switch表达式(简洁写法)
final icon = switch (status) {
'active' => Icons.check_circle,
'pending' => Icons.hourglass_empty,
'error' => Icons.error,
_ => Icons.help,
};Riverpod 3.0 Patterns
Riverpod 3.0模式
dart
// Modern Riverpod with code generation
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
// Async provider with Riverpod 3.0
Future<List<Item>> items(Ref ref) async {
final repository = ref.watch(repositoryProvider);
return repository.fetchItems();
}
// Family provider (parameterized)
Future<User> user(Ref ref, String userId) async {
return ref.watch(apiClientProvider).getUser(userId);
}
// Usage in widget
class ItemScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return items.when(
data: (data) => ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) => ItemTile(data[i]),
),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}dart
// 结合代码生成的现代Riverpod用法
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
// Riverpod 3.0异步Provider
Future<List<Item>> items(Ref ref) async {
final repository = ref.watch(repositoryProvider);
return repository.fetchItems();
}
// Family Provider(带参数)
Future<User> user(Ref ref, String userId) async {
return ref.watch(apiClientProvider).getUser(userId);
}
// 在组件中使用
class ItemScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return items.when(
data: (data) => ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) => ItemTile(data[i]),
),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}Material 3 / Material You
Material 3 / Material You
dart
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system,
);
// Dynamic color (Android 12+)
import 'package:dynamic_color/dynamic_color.dart';
DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return MaterialApp(
theme: ThemeData(
colorScheme: lightDynamic ?? defaultLightScheme,
useMaterial3: true,
),
);
},
);dart
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system,
);
// 动态色彩(Android 12+)
import 'package:dynamic_color/dynamic_color.dart';
DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
return MaterialApp(
theme: ThemeData(
colorScheme: lightDynamic ?? defaultLightScheme,
useMaterial3: true,
),
);
},
);Build and Deployment
构建与部署
Fastlane
Fastlane
ruby
undefinedruby
undefinedfastlane/Fastfile
fastlane/Fastfile
platform :ios do
lane :beta do
build_flutter_app
upload_to_testflight
end
end
platform :android do
lane :beta do
build_flutter_app
upload_to_play_store(track: 'internal')
end
end
undefinedplatform :ios do
lane :beta do
build_flutter_app
upload_to_testflight
end
end
platform :android do
lane :beta do
build_flutter_app
upload_to_play_store(track: 'internal')
end
end
undefinedCodemagic CI/CD
Codemagic CI/CD
yaml
undefinedyaml
undefinedcodemagic.yaml
codemagic.yaml
workflows:
flutter-release:
name: Flutter Release
environment:
flutter: stable
scripts:
- name: Build
script: flutter build appbundle --release
artifacts:
- build//outputs//*.aab
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT
track: internal
undefinedworkflows:
flutter-release:
name: Flutter发布流程
environment:
flutter: stable
scripts:
- name: 构建应用
script: flutter build appbundle --release
artifacts:
- build//outputs//*.aab
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT
track: internal
undefinedEAS Build (for Expo/RN comparison reference)
EAS Build(Expo/RN对比参考)
Flutter apps typically use Fastlane, Codemagic, or GitHub Actions for CI/CD rather than EAS.
Flutter应用通常使用Fastlane、Codemagic或GitHub Actions进行CI/CD,而非EAS。