
構建 Flutter 原型很容易。構建可擴充套件、在負載下效能良好且可多年維護的生產就緒型 Flutter 應用程式需要對架構、狀態管理、測試和部署工作流程有更深入的瞭解。本指南彌合了教程專案和實際應用程式之間的差距,涵蓋了專業 Flutter 團隊每天依賴的模式和實踐。適用於 Flutter 的
Clean 架構
Clean 架構將您的應用程式分為具有清晰邊界和依賴關係規則的不同層。這種分離使您的程式碼可測試、可維護並且獨立於外部框架和工具。
層結構
生產型 Flutter 應用程式通常遵循三層架構:
- 表示層- 小部件、頁面和狀態管理。該層依賴於領域層,但從不直接依賴於資料來源。
- 域層- 業務邏輯、實體和用例。該層對 Flutter 或任何外部包的依賴為零。它定義了資料層實現的儲存庫介面(抽象類)。
- 資料層- 儲存庫實現、API 客戶端、本地資料庫訪問和資料模型 (DTO)。該層實現領域層中定義的介面。
lib/
core/
error/
exceptions.dart
failures.dart
network/
network_info.dart
usecases/
usecase.dart
features/
authentication/
data/
datasources/
auth_remote_datasource.dart
auth_local_datasource.dart
models/
user_model.dart
repositories/
auth_repository_impl.dart
domain/
entities/
user.dart
repositories/
auth_repository.dart
usecases/
login.dart
register.dart
logout.dart
presentation/
bloc/
auth_bloc.dart
auth_event.dart
auth_state.dart
pages/
login_page.dart
register_page.dart
widgets/
login_form.dart依賴關係規則很嚴格:內層永遠不知道外層。領域層定義抽象儲存庫介面,資料層提供具體實現。這種控制反轉允許您交換資料來源而無需觸及業務邏輯。
狀態管理:BLoC 和 Riverpod
選擇正確的狀態管理解決方案是 Flutter 專案中最有影響力的架構決策之一。
BLoC 模式
BLoC(業務邏輯元件)使用流來管理狀態。事件流入,狀態流出。這種單向資料流使狀態變化可預測且易於除錯。
// Events
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
final String email;
final String password;
LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}
// States
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
}
// BLoC
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final LoginUseCase loginUseCase;
final LogoutUseCase logoutUseCase;
AuthBloc({
required this.loginUseCase,
required this.logoutUseCase,
}) : super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
on<LogoutRequested>(_onLogoutRequested);
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
final result = await loginUseCase(
LoginParams(email: event.email, password: event.password),
);
result.fold(
(failure) => emit(AuthError(failure.message)),
(user) => emit(AuthAuthenticated(user)),
);
}
Future<void> _onLogoutRequested(
LogoutRequested event,
Emitter<AuthState> emit,
) async {
await logoutUseCase();
emit(AuthInitial());
}
}Riverpod
Riverpod 提供更靈活、編譯安全的狀態管理方法。與 Provider 不同,Riverpod 不依賴於 widget 樹,從而更容易測試和組合。
// Define providers
final authRepositoryProvider = Provider<AuthRepository>((ref) {
return AuthRepositoryImpl(
remoteDatasource: ref.read(authRemoteDatasourceProvider),
localDatasource: ref.read(authLocalDatasourceProvider),
);
});
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.read(authRepositoryProvider));
});
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository _repository;
AuthNotifier(this._repository) : super(const AuthState.initial());
Future<void> login(String email, String password) async {
state = const AuthState.loading();
final result = await _repository.login(email, password);
state = result.fold(
(failure) => AuthState.error(failure.message),
(user) => AuthState.authenticated(user),
);
}
}
// Use in widgets
class LoginPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authStateProvider);
return authState.when(
initial: () => LoginForm(),
loading: () => const CircularProgressIndicator(),
authenticated: (user) => HomePage(user: user),
error: (message) => ErrorDisplay(message: message),
);
}
}依賴項注入
正確的依賴項注入對於可測試程式碼至關重要。get_it軟體包提供了一個簡單的服務定位器,可以很好地與乾淨的架構配合使用。
final sl = GetIt.instance;
void initDependencies() {
// External
sl.registerLazySingleton(() => Dio()..interceptors.add(AuthInterceptor()));
sl.registerLazySingleton(() => InternetConnectionChecker());
// Data sources
sl.registerLazySingleton<AuthRemoteDatasource>(
() => AuthRemoteDatasourceImpl(dio: sl()),
);
sl.registerLazySingleton<AuthLocalDatasource>(
() => AuthLocalDatasourceImpl(secureStorage: sl()),
);
// Repositories
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(
remoteDatasource: sl(),
localDatasource: sl(),
networkInfo: sl(),
),
);
// Use cases
sl.registerLazySingleton(() => LoginUseCase(sl()));
sl.registerLazySingleton(() => RegisterUseCase(sl()));
// BLoCs
sl.registerFactory(() => AuthBloc(
loginUseCase: sl(),
logoutUseCase: sl(),
));
}API 與 Dio 整合
Dio 是最流行的 Dart HTTP 客戶端,提供攔截器、全域性配置和 FormData 支援。使用型別安全的請求和響應處理來構建 API 層。
class ApiClient {
final Dio _dio;
ApiClient(this._dio) {
_dio.options = BaseOptions(
baseUrl: Environment.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
headers: {'Content-Type': 'application/json'},
);
_dio.interceptors.addAll([
AuthInterceptor(),
LogInterceptor(requestBody: true, responseBody: true),
RetryInterceptor(dio: _dio, retries: 3),
]);
}
Future<T> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
required T Function(dynamic data) parser,
}) async {
try {
final response = await _dio.get(path, queryParameters: queryParameters);
return parser(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
AppException _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
return NetworkException('Connection timed out');
case DioExceptionType.badResponse:
return ServerException(
error.response?.statusCode ?? 500,
error.response?.data?['message'] ?? 'Unknown error',
);
default:
return NetworkException('Network error occurred');
}
}
}具有 Hive 和 Sqflite 的本地儲存
大多數生產應用程式需要本地資料永續性。根據您的資料複雜性選擇正確的工具。
Hive,用於鍵值和物件儲存
Hive 是一個用純 Dart 編寫的輕量級、速度極快的 NoSQL 資料庫。它非常適合快取、使用者首選項和儲存中小型資料集。
@HiveType(typeId: 0)
class CachedArticle extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String title;
@HiveField(2)
final String content;
@HiveField(3)
final DateTime cachedAt;
CachedArticle({
required this.id,
required this.title,
required this.content,
required this.cachedAt,
});
}
class ArticleCacheService {
static const _boxName = 'articles_cache';
Future<void> cacheArticles(List<Article> articles) async {
final box = await Hive.openBox<CachedArticle>(_boxName);
final cached = articles.map((a) => CachedArticle(
id: a.id,
title: a.title,
content: a.content,
cachedAt: DateTime.now(),
));
await box.clear();
await box.addAll(cached);
}
Future<List<CachedArticle>> getCachedArticles() async {
final box = await Hive.openBox<CachedArticle>(_boxName);
return box.values.toList();
}
}用於關係資料的 Sqflite
當您的資料具有複雜關係並且需要 SQL 查詢時,Sqflite 為 Flutter 提供完整的 SQLite 實現。將其用於受益於聯接、索引和事務的結構化資料。
推送通知
使用 Firebase 雲訊息傳遞 (FCM) 以及適當的許可權處理和後臺訊息處理來實現推送通知。
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// Request permission
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// Get FCM token
final token = await _messaging.getToken();
await _sendTokenToServer(token);
// Listen for token refresh
_messaging.onTokenRefresh.listen(_sendTokenToServer);
// Handle foreground messages
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
// Handle background/terminated message taps
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageTap);
}
}
void _handleForegroundMessage(RemoteMessage message) {
// Show local notification using flutter_local_notifications
FlutterLocalNotificationsPlugin().show(
message.hashCode,
message.notification?.title,
message.notification?.body,
const NotificationDetails(
android: AndroidNotificationDetails(
'default_channel',
'Default',
importance: Importance.high,
),
),
);
}
}深層連結
深層連結允許使用者從外部 URL 直接導航到應用程式內的特定內容。 Flutter同時支援基於URI的深度連結和動態連結。
// Configure in MaterialApp
MaterialApp(
onGenerateRoute: (settings) {
final uri = Uri.parse(settings.name ?? '');
if (uri.pathSegments.first == 'product') {
final productId = uri.pathSegments[1];
return MaterialPageRoute(
builder: (_) => ProductDetailPage(id: productId),
);
}
if (uri.pathSegments.first == 'order') {
final orderId = uri.pathSegments[1];
return MaterialPageRoute(
builder: (_) => OrderTrackingPage(id: orderId),
);
}
return MaterialPageRoute(builder: (_) => const HomePage());
},
)要獲得更強大的深度連結,請使用go_router軟體包,該軟體包提供具有深度連結支援、重定向和巢狀導航的宣告式路由。
帶 Codemagic 和 Fastlane 的 CI/CD
自動化構建和部署管道對於生產應用程式至關重要。 Codemagic 提供 Flutter 原生 CI/CD 服務,而 Fastlane 提供更多可定製的自動化。
Codemagic 配置
# codemagic.yaml
workflows:
production-release:
name: Production Release
max_build_duration: 60
environment:
flutter: stable
vars:
APP_STORE_CONNECT_KEY_ID: Encrypted(...)
GOOGLE_PLAY_SERVICE_ACCOUNT: Encrypted(...)
scripts:
- name: Install dependencies
script: flutter pub get
- name: Run tests
script: flutter test --coverage
- name: Build Android
script: flutter build appbundle --release
- name: Build iOS
script: |
flutter build ipa --release \
--export-options-plist=/path/to/ExportOptions.plist
artifacts:
- build/**/outputs/**/*.aab
- build/ios/ipa/*.ipa
publishing:
google_play:
credentials: $GOOGLE_PLAY_SERVICE_ACCOUNT
track: internal
app_store_connect:
api_key: $APP_STORE_CONNECT_KEY_IDFastlane 整合
Fastlane 提供對構建和提交過程的精細控制。為不同的釋出階段定義通道:
# fastlane/Fastfile
platform :ios do
desc "Deploy to TestFlight"
lane :beta do
build_flutter_app(target: "lib/main.dart")
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
desc "Deploy to App Store"
lane :release do
build_flutter_app(target: "lib/main.dart")
upload_to_app_store(
submit_for_review: true,
automatic_release: false
)
end
end效能分析
生產應用程式需要一致的效能。 Flutter DevTools 提供全面的分析功能。
- 小部件重建跟蹤- 使用效能覆蓋和開發工具來識別過度重建的小部件。應用
const建構函式和選擇性狀態管理來最大限度地減少重建。 - 幀渲染- 監控時間線檢視以確保幀在 16ms (60fps) 或 8ms (120fps) 內渲染。尋找昂貴的構建、佈局和油漆階段。
- 記憶體分析- 跟蹤記憶體分配以檢測洩漏。常見的罪魁禍首包括未取消的流訂閱、未處理的控制器和閉包中保留的引用。
- 啟動效能- 使用
WidgetsBinding.instance.addPostFrameCallback推遲繁重的初始化。對於不立即需要的功能,使用deferred as匯入的延遲載入。
// Profile-mode build for accurate performance measurement
// flutter run --profile
// Add performance overlay in debug builds
MaterialApp(
showPerformanceOverlay: true,
// ...
)結論
構建可投入生產的 Flutter 應用程式需要的不僅僅是瞭解小部件目錄。 它需要深思熟慮的架構、強大的狀態管理、全面的測試和自動化部署管道。透過採用乾淨的架構、投資適當的依賴項注入、實施徹底的錯誤處理以及建立 CI/CD 工作流程,您建立的應用程式不僅可以正常執行,而且可以長期維護和擴充套件。
首先儘早建立架構,從第一天開始編寫測試,並在首次釋出之前自動化部署管道。隨著時間的推移,這些前期投資會不斷增加,使您的團隊能夠更快地釋出功能,減少迴歸,並對每個版本更有信心。