Flutter
Mobile Platform
Flutter
Overview
Flutter is an innovative cross-platform UI framework developed by Google that enables building iOS, Android, Web, Windows, macOS, and Linux applications from a single codebase. Through type-safe development with the Dart language, smooth 60fps animations with its custom Skia rendering engine, and the realization of "Write Once, Run Natively Everywhere" beyond "Write Once, Run Anywhere," it achieves both native performance and cross-platform development efficiency. As of 2025, with the stabilization of the Impeller rendering engine from Flutter 3.16, WebAssembly (WASM) support, Enhanced Hot Reload, and AI integration features, it stands at the forefront of next-generation multi-platform development.
Details
Flutter 2025 edition achieves true native graphics performance through the complete stabilization of the Impeller rendering engine, breaking through traditional Skia-based limitations with Metal (iOS) and Vulkan (Android). Particularly noteworthy is the significant performance improvement in web applications through WebAssembly (WASM) support, and the deep integration with other platforms through Element Embedding. With Dart 3.3's enhanced asynchronous processing, Package Ecosystem maturity, AI assistant integration in DevTools, and complete support for Material Design 3 (Material You), it provides a comprehensive development ecosystem that simultaneously improves developer productivity and application quality.
Key Features
- Universal Platform: Support for 6 platforms (iOS/Android/Web/Windows/macOS/Linux)
- Impeller Rendering: High-speed graphics processing with Metal/Vulkan
- Hot Reload: Instant code change reflection with state preservation
- Dart Language: Type-safe, high-performance modern language
- Rich Widgets: Material Design, Cupertino, and custom widgets
- Native Integration: Direct access to platform-specific functionality
Latest 2025 Features
- Impeller Engine Stable: Native Metal/Vulkan rendering on iOS/Android
- WebAssembly Support: Significant performance improvements for web applications
- AI Integration Features: Gemini AI API integration and code generation assistance
- Enhanced Hot Reload: Faster and more stable hot reload
- Material Design 3: Complete implementation of Material You
- Element Embedding: Enhanced integration with other frameworks
Advantages and Disadvantages
Advantages
- Single codebase supporting 6 platforms
- Native-level performance with smooth animations
- Extremely fast development cycles with Hot Reload
- Rich and beautiful widget library
- Type-safe and readable code with Dart language
- Continuous development by Google with proven enterprise track record
- Active community and rich package ecosystem
Disadvantages
- Learning cost and adoption barriers for Dart language
- Tendency for larger app sizes compared to native development
- Access limitations to deep platform-specific functionality
- Developing ecosystem for platforms other than Android/iOS
- Development costs for complex native integrations
- SEO and accessibility constraints on the web
Reference Pages
Code Examples
Setup and Basic Configuration
# Flutter SDK installation (recommended method)
# 1. Download SDK from Flutter official site
# 2. Set path
export PATH="$PATH:[PATH_TO_FLUTTER_GIT_DIRECTORY]/flutter/bin"
# Verify Flutter installation
flutter --version
# Check development environment
flutter doctor
# Create new project
flutter create my_awesome_app
cd my_awesome_app
# Get dependencies
flutter pub get
# Run on iOS simulator (macOS only)
flutter run -d ios
# Run on Android emulator
flutter run -d android
# Run on web browser
flutter run -d web-server --web-hostname localhost --web-port 8080
# Run as desktop app (macOS)
flutter run -d macos
# Run as desktop app (Windows)
flutter run -d windows
# Run as desktop app (Linux)
flutter run -d linux
# Update dependencies
flutter pub upgrade
# Clean project
flutter clean
// main.dart - Application entry point
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart';
import 'services/auth_service.dart';
import 'services/api_service.dart';
import 'screens/splash_screen.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Firebase initialization
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthService()),
Provider(create: (_) => ApiService()),
],
child: MaterialApp(
title: 'Flutter Demo App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const SplashScreen(),
debugShowCheckedModeBanner: false,
),
);
}
}
Authentication Service
// services/auth_service.dart - Authentication management
import 'package:flutter/foundation.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'dart:math';
class AuthUser {
final String uid;
final String email;
final String? displayName;
final String? photoURL;
final bool emailVerified;
AuthUser({
required this.uid,
required this.email,
this.displayName,
this.photoURL,
required this.emailVerified,
});
factory AuthUser.fromFirebaseUser(User user) {
return AuthUser(
uid: user.uid,
email: user.email!,
displayName: user.displayName,
photoURL: user.photoURL,
emailVerified: user.emailVerified,
);
}
}
class AuthService extends ChangeNotifier {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
AuthUser? _currentUser;
AuthUser? get currentUser => _currentUser;
bool get isAuthenticated => _currentUser != null;
AuthService() {
_firebaseAuth.authStateChanges().listen(_onAuthStateChanged);
}
void _onAuthStateChanged(User? user) {
if (user != null) {
_currentUser = AuthUser.fromFirebaseUser(user);
} else {
_currentUser = null;
}
notifyListeners();
}
// Email/Password registration
Future<AuthUser> registerWithEmailAndPassword({
required String email,
required String password,
required String displayName,
}) async {
try {
final UserCredential result = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
final User user = result.user!;
// Update profile
await user.updateDisplayName(displayName);
await user.sendEmailVerification();
return AuthUser.fromFirebaseUser(user);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Email/Password login
Future<AuthUser> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
final UserCredential result = await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
return AuthUser.fromFirebaseUser(result.user!);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Google Sign-In
Future<AuthUser> signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) {
throw Exception('Google Sign-In was cancelled');
}
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final UserCredential result = await _firebaseAuth.signInWithCredential(credential);
return AuthUser.fromFirebaseUser(result.user!);
} catch (e) {
throw Exception('Google Sign-In failed: $e');
}
}
// Apple Sign-In
Future<AuthUser> signInWithApple() async {
try {
final rawNonce = _generateNonce();
final nonce = _sha256ofString(rawNonce);
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
final UserCredential result = await _firebaseAuth.signInWithCredential(oauthCredential);
return AuthUser.fromFirebaseUser(result.user!);
} catch (e) {
throw Exception('Apple Sign-In failed: $e');
}
}
// Password reset
Future<void> sendPasswordResetEmail(String email) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Email verification
Future<void> sendEmailVerification() async {
final user = _firebaseAuth.currentUser;
if (user != null && !user.emailVerified) {
await user.sendEmailVerification();
}
}
// Sign out
Future<void> signOut() async {
await Future.wait([
_firebaseAuth.signOut(),
_googleSignIn.signOut(),
]);
}
// Delete account
Future<void> deleteAccount() async {
final user = _firebaseAuth.currentUser;
if (user != null) {
await user.delete();
}
}
// Helper methods
String _generateNonce([int length = 32]) {
const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join();
}
String _sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Exception _handleAuthException(FirebaseAuthException e) {
String message;
switch (e.code) {
case 'weak-password':
message = 'The password provided is too weak.';
break;
case 'email-already-in-use':
message = 'The account already exists for that email.';
break;
case 'user-not-found':
message = 'No user found for that email.';
break;
case 'wrong-password':
message = 'Wrong password provided for that user.';
break;
case 'invalid-email':
message = 'The email address is not valid.';
break;
case 'user-disabled':
message = 'This user account has been disabled.';
break;
case 'too-many-requests':
message = 'Too many unsuccessful login attempts. Please try again later.';
break;
default:
message = 'An authentication error occurred: ${e.message}';
}
return Exception(message);
}
}
Backend Integration and API Service
// services/api_service.dart - API communication
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
class ApiResponse<T> {
final bool success;
final T? data;
final String? error;
final int? statusCode;
ApiResponse({
required this.success,
this.data,
this.error,
this.statusCode,
});
}
class RetryConfig {
final int maxRetries;
final Duration baseDelay;
final double backoffMultiplier;
const RetryConfig({
this.maxRetries = 3,
this.baseDelay = const Duration(seconds: 1),
this.backoffMultiplier = 2.0,
});
}
class ApiService {
static const String baseUrl = 'https://api.example.com';
static const Duration defaultTimeout = Duration(seconds: 30);
final http.Client _client = http.Client();
final RetryConfig _retryConfig = const RetryConfig();
String? _authToken;
// Set authentication token
void setAuthToken(String token) {
_authToken = token;
}
// Clear authentication token
void clearAuthToken() {
_authToken = null;
}
// GET request with retry logic
Future<ApiResponse<T>> get<T>(
String endpoint, {
Map<String, String>? headers,
Map<String, dynamic>? queryParams,
T Function(Map<String, dynamic>)? fromJson,
}) async {
return _executeWithRetry<T>(
() => _makeRequest<T>(
'GET',
endpoint,
headers: headers,
queryParams: queryParams,
fromJson: fromJson,
),
);
}
// POST request
Future<ApiResponse<T>> post<T>(
String endpoint, {
Map<String, dynamic>? body,
Map<String, String>? headers,
T Function(Map<String, dynamic>)? fromJson,
}) async {
return _executeWithRetry<T>(
() => _makeRequest<T>(
'POST',
endpoint,
body: body,
headers: headers,
fromJson: fromJson,
),
);
}
// PUT request
Future<ApiResponse<T>> put<T>(
String endpoint, {
Map<String, dynamic>? body,
Map<String, String>? headers,
T Function(Map<String, dynamic>)? fromJson,
}) async {
return _executeWithRetry<T>(
() => _makeRequest<T>(
'PUT',
endpoint,
body: body,
headers: headers,
fromJson: fromJson,
),
);
}
// DELETE request
Future<ApiResponse<T>> delete<T>(
String endpoint, {
Map<String, String>? headers,
T Function(Map<String, dynamic>)? fromJson,
}) async {
return _executeWithRetry<T>(
() => _makeRequest<T>(
'DELETE',
endpoint,
headers: headers,
fromJson: fromJson,
),
);
}
// Core HTTP request method
Future<ApiResponse<T>> _makeRequest<T>(
String method,
String endpoint, {
Map<String, dynamic>? body,
Map<String, String>? headers,
Map<String, dynamic>? queryParams,
T Function(Map<String, dynamic>)? fromJson,
}) async {
try {
final uri = _buildUri(endpoint, queryParams);
final requestHeaders = _buildHeaders(headers);
http.Response response;
switch (method.toUpperCase()) {
case 'GET':
response = await _client.get(uri, headers: requestHeaders).timeout(defaultTimeout);
break;
case 'POST':
response = await _client
.post(uri, headers: requestHeaders, body: json.encode(body))
.timeout(defaultTimeout);
break;
case 'PUT':
response = await _client
.put(uri, headers: requestHeaders, body: json.encode(body))
.timeout(defaultTimeout);
break;
case 'DELETE':
response = await _client.delete(uri, headers: requestHeaders).timeout(defaultTimeout);
break;
default:
throw Exception('Unsupported HTTP method: $method');
}
return _handleResponse<T>(response, fromJson);
} on SocketException {
return ApiResponse<T>(
success: false,
error: 'No internet connection',
);
} on HttpException {
return ApiResponse<T>(
success: false,
error: 'HTTP error occurred',
);
} on FormatException {
return ApiResponse<T>(
success: false,
error: 'Invalid response format',
);
} catch (e) {
return ApiResponse<T>(
success: false,
error: 'Request failed: $e',
);
}
}
// Retry logic implementation
Future<ApiResponse<T>> _executeWithRetry<T>(
Future<ApiResponse<T>> Function() request,
) async {
int attempt = 0;
while (attempt < _retryConfig.maxRetries) {
final response = await request();
if (response.success || !_shouldRetry(response.statusCode)) {
return response;
}
attempt++;
if (attempt < _retryConfig.maxRetries) {
final delay = Duration(
milliseconds: (_retryConfig.baseDelay.inMilliseconds *
pow(_retryConfig.backoffMultiplier, attempt - 1))
.round(),
);
await Future.delayed(delay);
}
}
return ApiResponse<T>(
success: false,
error: 'Request failed after ${_retryConfig.maxRetries} attempts',
);
}
// Response processing
ApiResponse<T> _handleResponse<T>(
http.Response response,
T Function(Map<String, dynamic>)? fromJson,
) {
final statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
try {
final jsonData = json.decode(response.body) as Map<String, dynamic>;
T? data;
if (fromJson != null) {
data = fromJson(jsonData);
} else if (T == Map<String, dynamic>) {
data = jsonData as T;
}
return ApiResponse<T>(
success: true,
data: data,
statusCode: statusCode,
);
} catch (e) {
return ApiResponse<T>(
success: false,
error: 'Failed to parse response: $e',
statusCode: statusCode,
);
}
} else {
String errorMessage = 'Request failed';
try {
final errorData = json.decode(response.body) as Map<String, dynamic>;
errorMessage = errorData['message'] ?? errorData['error'] ?? errorMessage;
} catch (e) {
errorMessage = 'HTTP $statusCode: ${response.reasonPhrase}';
}
return ApiResponse<T>(
success: false,
error: errorMessage,
statusCode: statusCode,
);
}
}
// URI construction
Uri _buildUri(String endpoint, Map<String, dynamic>? queryParams) {
final uri = Uri.parse('$baseUrl$endpoint');
if (queryParams != null && queryParams.isNotEmpty) {
return uri.replace(queryParameters: queryParams.map((k, v) => MapEntry(k, v.toString())));
}
return uri;
}
// Header construction
Map<String, String> _buildHeaders(Map<String, String>? customHeaders) {
final headers = <String, String>{
'Content-Type': 'application/json',
'Accept': 'application/json',
};
if (_authToken != null) {
headers['Authorization'] = 'Bearer $_authToken';
}
if (customHeaders != null) {
headers.addAll(customHeaders);
}
return headers;
}
// Retry condition check
bool _shouldRetry(int? statusCode) {
if (statusCode == null) return true;
return statusCode >= 500 || statusCode == 408 || statusCode == 429;
}
// Cleanup
void dispose() {
_client.close();
}
}
// Usage example models
class User {
final String id;
final String name;
final String email;
final DateTime createdAt;
User({
required this.id,
required this.name,
required this.email,
required this.createdAt,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
createdAt: DateTime.parse(json['created_at']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'created_at': createdAt.toIso8601String(),
};
}
}
// API service usage example
class UserRepository {
final ApiService _apiService;
UserRepository(this._apiService);
Future<List<User>> getUsers() async {
final response = await _apiService.get<List<User>>(
'/users',
fromJson: (json) {
final userList = json['data'] as List;
return userList.map((userJson) => User.fromJson(userJson)).toList();
},
);
if (response.success && response.data != null) {
return response.data!;
} else {
throw Exception(response.error ?? 'Failed to fetch users');
}
}
Future<User> createUser(User user) async {
final response = await _apiService.post<User>(
'/users',
body: user.toJson(),
fromJson: (json) => User.fromJson(json['data']),
);
if (response.success && response.data != null) {
return response.data!;
} else {
throw Exception(response.error ?? 'Failed to create user');
}
}
}
Push Notification Implementation
// services/notification_service.dart - Push notification management
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class NotificationService {
static final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
static final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin();
static const AndroidNotificationChannel _androidChannel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.high,
);
// Initialization
static Future<void> initialize() async {
// Request permission
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
if (kDebugMode) {
print('User granted permission');
}
} else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
if (kDebugMode) {
print('User granted provisional permission');
}
} else {
if (kDebugMode) {
print('User declined or has not accepted permission');
}
}
// Initialize local notifications
await _initializeLocalNotifications();
// Create Android notification channel
await _localNotifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(_androidChannel);
// Setup message handlers
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageOpenedApp);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Handle initial message when app is launched from terminated state
RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
if (initialMessage != null) {
_handleMessageOpenedApp(initialMessage);
}
}
// Local notifications initialization
static Future<void> _initializeLocalNotifications() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsDarwin =
DarwinInitializationSettings(
requestSoundPermission: true,
requestBadgePermission: true,
requestAlertPermission: true,
);
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
macOS: initializationSettingsDarwin,
);
await _localNotifications.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
);
}
// FCM token retrieval
static Future<String?> getToken() async {
try {
return await _firebaseMessaging.getToken();
} catch (e) {
if (kDebugMode) {
print('Error getting FCM token: $e');
}
return null;
}
}
// Token update monitoring
static void onTokenRefresh(Function(String) onTokenReceived) {
_firebaseMessaging.onTokenRefresh.listen(onTokenReceived);
}
// Topic subscription
static Future<void> subscribeToTopic(String topic) async {
try {
await _firebaseMessaging.subscribeToTopic(topic);
if (kDebugMode) {
print('Subscribed to topic: $topic');
}
} catch (e) {
if (kDebugMode) {
print('Error subscribing to topic $topic: $e');
}
}
}
// Topic unsubscription
static Future<void> unsubscribeFromTopic(String topic) async {
try {
await _firebaseMessaging.unsubscribeFromTopic(topic);
if (kDebugMode) {
print('Unsubscribed from topic: $topic');
}
} catch (e) {
if (kDebugMode) {
print('Error unsubscribing from topic $topic: $e');
}
}
}
// Local notification display
static Future<void> showLocalNotification({
required String title,
required String body,
String? payload,
int id = 0,
}) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
icon: '@mipmap/ic_launcher',
);
const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: iosNotificationDetails,
macOS: iosNotificationDetails,
);
await _localNotifications.show(
id,
title,
body,
notificationDetails,
payload: payload,
);
}
// Scheduled notification
static Future<void> scheduleNotification({
required int id,
required String title,
required String body,
required DateTime scheduledDate,
String? payload,
}) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'scheduled_channel',
'Scheduled Notifications',
channelDescription: 'This channel is used for scheduled notifications.',
importance: Importance.max,
priority: Priority.high,
);
const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: iosNotificationDetails,
macOS: iosNotificationDetails,
);
await _localNotifications.zonedSchedule(
id,
title,
body,
tz.TZDateTime.from(scheduledDate, tz.local),
notificationDetails,
payload: payload,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
);
}
// Cancel notification
static Future<void> cancelNotification(int id) async {
await _localNotifications.cancel(id);
}
// Cancel all notifications
static Future<void> cancelAllNotifications() async {
await _localNotifications.cancelAll();
}
// Foreground message handler
static void _handleForegroundMessage(RemoteMessage message) {
if (kDebugMode) {
print('Handling a foreground message: ${message.messageId}');
}
// Display local notification for foreground messages
showLocalNotification(
title: message.notification?.title ?? 'New Message',
body: message.notification?.body ?? 'You have a new message',
payload: message.data.toString(),
);
}
// Message opened handler
static void _handleMessageOpenedApp(RemoteMessage message) {
if (kDebugMode) {
print('A new onMessageOpenedApp event was published!');
print('Message data: ${message.data}');
}
// Navigate to specific screen based on message data
final String? screenPath = message.data['screen'];
if (screenPath != null) {
_navigateToScreen(screenPath, message.data);
}
}
// Navigation helper
static void _navigateToScreen(String screenPath, Map<String, dynamic> data) {
// Implementation depends on your navigation setup
// Example with Navigator
final BuildContext? context = navigatorKey.currentContext;
if (context != null) {
switch (screenPath) {
case '/profile':
Navigator.pushNamed(context, '/profile', arguments: data);
break;
case '/chat':
Navigator.pushNamed(context, '/chat', arguments: data);
break;
default:
Navigator.pushNamed(context, '/home');
}
}
}
// Local notification response handler
static void _onDidReceiveNotificationResponse(NotificationResponse response) {
if (kDebugMode) {
print('Local notification tapped with payload: ${response.payload}');
}
// Handle local notification tap
if (response.payload != null) {
// Parse payload and navigate accordingly
try {
final Map<String, dynamic> data = json.decode(response.payload!);
final String? screenPath = data['screen'];
if (screenPath != null) {
_navigateToScreen(screenPath, data);
}
} catch (e) {
if (kDebugMode) {
print('Error parsing notification payload: $e');
}
}
}
}
}
// Background message handler (must be top-level function)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Initialize Firebase if needed
await Firebase.initializeApp();
if (kDebugMode) {
print('Handling a background message: ${message.messageId}');
print('Message data: ${message.data}');
}
// Handle background message
// Can save to local database, show notification, etc.
}
// Global navigator key for navigation from static context
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Analytics Integration
// services/analytics_service.dart - Analytics and performance monitoring
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_performance/firebase_performance.dart';
import 'package:flutter/foundation.dart';
class AnalyticsService {
static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
static final FirebaseCrashlytics _crashlytics = FirebaseCrashlytics.instance;
static final FirebasePerformance _performance = FirebasePerformance.instance;
// Initialize analytics
static Future<void> initialize() async {
// Set analytics collection enabled
await _analytics.setAnalyticsCollectionEnabled(true);
// Enable Crashlytics collection in debug mode
await _crashlytics.setCrashlyticsCollectionEnabled(true);
// Set user properties
await setUserProperties();
if (kDebugMode) {
print('Analytics service initialized');
}
}
// Set user ID
static Future<void> setUserId(String userId) async {
await _analytics.setUserId(id: userId);
await _crashlytics.setUserIdentifier(userId);
}
// Set user properties
static Future<void> setUserProperties({
String? userType,
String? subscriptionTier,
String? language,
}) async {
if (userType != null) {
await _analytics.setUserProperty(name: 'user_type', value: userType);
}
if (subscriptionTier != null) {
await _analytics.setUserProperty(name: 'subscription_tier', value: subscriptionTier);
}
if (language != null) {
await _analytics.setUserProperty(name: 'language', value: language);
}
}
// Log screen view
static Future<void> logScreenView({
required String screenName,
String? screenClass,
Map<String, dynamic>? parameters,
}) async {
await _analytics.logScreenView(
screenName: screenName,
screenClass: screenClass,
);
if (parameters != null) {
await _analytics.logEvent(
name: 'screen_view_detailed',
parameters: {
'screen_name': screenName,
'screen_class': screenClass ?? 'unknown',
...parameters,
},
);
}
}
// Log custom event
static Future<void> logEvent({
required String name,
Map<String, dynamic>? parameters,
}) async {
// Convert parameters to Analytics-compatible format
final Map<String, Object>? analyticsParameters = parameters?.map(
(key, value) => MapEntry(key, value.toString()),
);
await _analytics.logEvent(
name: name,
parameters: analyticsParameters,
);
}
// E-commerce events
static Future<void> logPurchase({
required String transactionId,
required double value,
required String currency,
required List<AnalyticsEventItem> items,
String? coupon,
}) async {
await _analytics.logPurchase(
transactionId: transactionId,
value: value,
currency: currency,
items: items,
coupon: coupon,
);
}
static Future<void> logAddToCart({
required String itemId,
required String itemName,
required String itemCategory,
required double value,
required String currency,
}) async {
await _analytics.logAddToCart(
itemId: itemId,
itemName: itemName,
itemCategory: itemCategory,
value: value,
currency: currency,
);
}
// User engagement events
static Future<void> logLogin({String? method}) async {
await _analytics.logLogin(loginMethod: method);
}
static Future<void> logSignUp({String? method}) async {
await _analytics.logSignUp(signUpMethod: method);
}
static Future<void> logShare({
required String contentType,
required String itemId,
String? method,
}) async {
await _analytics.logShare(
contentType: contentType,
itemId: itemId,
method: method,
);
}
// Custom business events
static Future<void> logFeatureUsage({
required String featureName,
Map<String, dynamic>? context,
}) async {
await logEvent(
name: 'feature_usage',
parameters: {
'feature_name': featureName,
'timestamp': DateTime.now().millisecondsSinceEpoch,
if (context != null) ...context,
},
);
}
static Future<void> logUserEngagement({
required String action,
required String target,
Map<String, dynamic>? metadata,
}) async {
await logEvent(
name: 'user_engagement',
parameters: {
'action': action,
'target': target,
'timestamp': DateTime.now().millisecondsSinceEpoch,
if (metadata != null) ...metadata,
},
);
}
// Performance monitoring
static Trace startTrace(String traceName) {
return _performance.newTrace(traceName);
}
static Future<T> measureOperation<T>({
required String operationName,
required Future<T> Function() operation,
Map<String, String>? attributes,
}) async {
final trace = _performance.newTrace(operationName);
if (attributes != null) {
attributes.forEach((key, value) {
trace.setAttributes(key, value);
});
}
await trace.start();
try {
final result = await operation();
trace.setMetric('success', 1);
return result;
} catch (e) {
trace.setMetric('error', 1);
await recordError(e, 'Performance trace: $operationName');
rethrow;
} finally {
await trace.stop();
}
}
// HTTP performance monitoring
static HttpMetric newHttpMetric({
required String url,
required HttpMethod httpMethod,
}) {
return _performance.newHttpMetric(url, httpMethod);
}
// Error and crash reporting
static Future<void> recordError(
dynamic exception, [
StackTrace? stackTrace,
String? reason,
bool fatal = false,
]) async {
await _crashlytics.recordError(
exception,
stackTrace,
reason: reason,
fatal: fatal,
);
// Also log to analytics for non-fatal errors
if (!fatal) {
await logEvent(
name: 'non_fatal_error',
parameters: {
'error_type': exception.runtimeType.toString(),
'error_message': exception.toString(),
'reason': reason ?? 'unknown',
},
);
}
}
static Future<void> recordFlutterError(FlutterErrorDetails errorDetails) async {
await _crashlytics.recordFlutterFatalError(errorDetails);
}
static Future<void> recordFlutterFatalError(FlutterErrorDetails errorDetails) async {
await _crashlytics.recordFlutterFatalError(errorDetails);
}
// Log custom message
static Future<void> log(String message) async {
await _crashlytics.log(message);
}
// Set custom key-value pairs
static Future<void> setCustomKey(String key, dynamic value) async {
await _crashlytics.setCustomKey(key, value);
}
// Test crash (for testing purposes only)
static Future<void> testCrash() async {
if (kDebugMode) {
await _crashlytics.crash();
}
}
// Application lifecycle events
static Future<void> logAppStart() async {
await logEvent(
name: 'app_start',
parameters: {
'timestamp': DateTime.now().millisecondsSinceEpoch,
'platform': defaultTargetPlatform.name,
},
);
}
static Future<void> logAppExit() async {
await logEvent(
name: 'app_exit',
parameters: {
'timestamp': DateTime.now().millisecondsSinceEpoch,
'session_duration': _getSessionDuration(),
},
);
}
// Helper methods
static int _getSessionDuration() {
// Implementation depends on your session tracking
return 0; // Placeholder
}
// Consent management
static Future<void> setAnalyticsConsentGranted(bool granted) async {
await _analytics.setAnalyticsCollectionEnabled(granted);
await _crashlytics.setCrashlyticsCollectionEnabled(granted);
}
// A/B testing support
static Future<void> logExperimentParticipation({
required String experimentId,
required String variantId,
}) async {
await logEvent(
name: 'experiment_participation',
parameters: {
'experiment_id': experimentId,
'variant_id': variantId,
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
);
}
}
// Custom analytics event wrapper
class CustomAnalyticsEvent {
final String name;
final Map<String, dynamic> parameters;
final DateTime timestamp;
CustomAnalyticsEvent({
required this.name,
required this.parameters,
DateTime? timestamp,
}) : timestamp = timestamp ?? DateTime.now();
Future<void> log() async {
await AnalyticsService.logEvent(
name: name,
parameters: {
...parameters,
'event_timestamp': timestamp.millisecondsSinceEpoch,
},
);
}
}
// Performance monitoring helper
class PerformanceMonitor {
static final Map<String, Stopwatch> _timers = {};
static void startTimer(String name) {
_timers[name] = Stopwatch()..start();
}
static Future<void> stopTimer(String name, [Map<String, String>? attributes]) async {
final timer = _timers.remove(name);
if (timer != null) {
timer.stop();
await AnalyticsService.logEvent(
name: 'performance_timing',
parameters: {
'operation': name,
'duration_ms': timer.elapsedMilliseconds,
'timestamp': DateTime.now().millisecondsSinceEpoch,
if (attributes != null) ...attributes,
},
);
}
}
static Future<T> timeOperation<T>({
required String name,
required Future<T> Function() operation,
Map<String, String>? attributes,
}) async {
return await AnalyticsService.measureOperation(
operationName: name,
operation: operation,
attributes: attributes,
);
}
}
Performance Monitoring
// services/performance_service.dart - Performance monitoring and optimization
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:firebase_performance/firebase_performance.dart';
class PerformanceService {
static final FirebasePerformance _performance = FirebasePerformance.instance;
static final Map<String, Trace> _activeTraces = {};
static final Map<String, DateTime> _operationStartTimes = {};
// Initialize performance monitoring
static Future<void> initialize() async {
// Enable performance collection
await _performance.setPerformanceCollectionEnabled(true);
if (kDebugMode) {
print('Performance monitoring initialized');
}
}
// Start custom trace
static Future<void> startTrace(String traceName) async {
if (_activeTraces.containsKey(traceName)) {
if (kDebugMode) {
print('Trace $traceName is already active');
}
return;
}
final trace = _performance.newTrace(traceName);
_activeTraces[traceName] = trace;
await trace.start();
if (kDebugMode) {
print('Started trace: $traceName');
}
}
// Stop custom trace
static Future<void> stopTrace(String traceName) async {
final trace = _activeTraces.remove(traceName);
if (trace != null) {
await trace.stop();
if (kDebugMode) {
print('Stopped trace: $traceName');
}
} else {
if (kDebugMode) {
print('Trace $traceName not found');
}
}
}
// Add metric to trace
static void incrementMetric(String traceName, String metricName, [int value = 1]) {
final trace = _activeTraces[traceName];
if (trace != null) {
trace.incrementMetric(metricName, value);
}
}
// Set metric value
static void setMetric(String traceName, String metricName, int value) {
final trace = _activeTraces[traceName];
if (trace != null) {
trace.setMetric(metricName, value);
}
}
// Add attributes to trace
static void setAttribute(String traceName, String attributeName, String value) {
final trace = _activeTraces[traceName];
if (trace != null) {
trace.setAttribute(attributeName, value);
}
}
// HTTP request monitoring
static HttpMetric createHttpMetric(String url, HttpMethod method) {
return _performance.newHttpMetric(url, method);
}
// Screen rendering performance
static Future<void> measureScreenLoad({
required String screenName,
required Future<void> Function() loadOperation,
}) async {
final traceName = 'screen_load_$screenName';
await startTrace(traceName);
setAttribute(traceName, 'screen_name', screenName);
final stopwatch = Stopwatch()..start();
try {
await loadOperation();
stopwatch.stop();
setMetric(traceName, 'load_time_ms', stopwatch.elapsedMilliseconds);
setMetric(traceName, 'success', 1);
} catch (e) {
stopwatch.stop();
setMetric(traceName, 'error', 1);
setAttribute(traceName, 'error_type', e.runtimeType.toString());
rethrow;
} finally {
await stopTrace(traceName);
}
}
// Database operation monitoring
static Future<T> measureDatabaseOperation<T>({
required String operationName,
required Future<T> Function() operation,
String? tableName,
}) async {
final traceName = 'db_$operationName';
await startTrace(traceName);
setAttribute(traceName, 'operation', operationName);
if (tableName != null) {
setAttribute(traceName, 'table', tableName);
}
final stopwatch = Stopwatch()..start();
try {
final result = await operation();
stopwatch.stop();
setMetric(traceName, 'duration_ms', stopwatch.elapsedMilliseconds);
setMetric(traceName, 'success', 1);
return result;
} catch (e) {
stopwatch.stop();
setMetric(traceName, 'error', 1);
setAttribute(traceName, 'error_type', e.runtimeType.toString());
rethrow;
} finally {
await stopTrace(traceName);
}
}
// Network operation monitoring
static Future<T> measureNetworkOperation<T>({
required String operationName,
required String endpoint,
required Future<T> Function() operation,
}) async {
final traceName = 'network_$operationName';
await startTrace(traceName);
setAttribute(traceName, 'endpoint', endpoint);
setAttribute(traceName, 'operation', operationName);
final stopwatch = Stopwatch()..start();
try {
final result = await operation();
stopwatch.stop();
setMetric(traceName, 'duration_ms', stopwatch.elapsedMilliseconds);
setMetric(traceName, 'success', 1);
return result;
} catch (e) {
stopwatch.stop();
setMetric(traceName, 'error', 1);
setAttribute(traceName, 'error_type', e.runtimeType.toString());
rethrow;
} finally {
await stopTrace(traceName);
}
}
// File I/O operation monitoring
static Future<T> measureFileOperation<T>({
required String operationName,
required String fileName,
required Future<T> Function() operation,
}) async {
final traceName = 'file_$operationName';
await startTrace(traceName);
setAttribute(traceName, 'operation', operationName);
setAttribute(traceName, 'file', fileName);
final stopwatch = Stopwatch()..start();
try {
final result = await operation();
stopwatch.stop();
setMetric(traceName, 'duration_ms', stopwatch.elapsedMilliseconds);
setMetric(traceName, 'success', 1);
return result;
} catch (e) {
stopwatch.stop();
setMetric(traceName, 'error', 1);
setAttribute(traceName, 'error_type', e.runtimeType.toString());
rethrow;
} finally {
await stopTrace(traceName);
}
}
// App startup monitoring
static Future<void> measureAppStartup() async {
const traceName = 'app_startup';
await startTrace(traceName);
setAttribute(traceName, 'platform', defaultTargetPlatform.name);
// This should be called after app initialization is complete
WidgetsBinding.instance.addPostFrameCallback((_) async {
setMetric(traceName, 'startup_complete', 1);
await stopTrace(traceName);
});
}
// Memory usage monitoring
static Future<void> measureMemoryUsage() async {
const traceName = 'memory_usage';
await startTrace(traceName);
try {
// Get memory info (platform-specific implementation needed)
final int memoryUsage = await _getMemoryUsage();
setMetric(traceName, 'memory_mb', memoryUsage);
} catch (e) {
setAttribute(traceName, 'error', e.toString());
} finally {
await stopTrace(traceName);
}
}
// Frame rendering monitoring
static void startFrameMonitoring() {
WidgetsBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
for (final timing in timings) {
final buildDuration = timing.buildDuration.inMilliseconds;
final rasterDuration = timing.rasterDuration.inMilliseconds;
if (buildDuration > 16 || rasterDuration > 16) { // 60fps = 16.67ms per frame
_logSlowFrame(buildDuration, rasterDuration);
}
}
});
}
// Log slow frame
static void _logSlowFrame(int buildMs, int rasterMs) {
if (kDebugMode) {
print('Slow frame detected: build ${buildMs}ms, raster ${rasterMs}ms');
}
// You can send this to your analytics service
// AnalyticsService.logEvent(
// name: 'slow_frame',
// parameters: {
// 'build_duration_ms': buildMs,
// 'raster_duration_ms': rasterMs,
// },
// );
}
// Platform-specific memory usage (simplified)
static Future<int> _getMemoryUsage() async {
try {
const platform = MethodChannel('com.example.app/memory');
final int? memoryMB = await platform.invokeMethod('getMemoryUsage');
return memoryMB ?? 0;
} catch (e) {
if (kDebugMode) {
print('Error getting memory usage: $e');
}
return 0;
}
}
// Cache performance monitoring
static Future<T> measureCacheOperation<T>({
required String cacheType,
required String operation,
required Future<T> Function() cacheOperation,
}) async {
final traceName = 'cache_${cacheType}_$operation';
await startTrace(traceName);
setAttribute(traceName, 'cache_type', cacheType);
setAttribute(traceName, 'operation', operation);
final stopwatch = Stopwatch()..start();
try {
final result = await cacheOperation();
stopwatch.stop();
setMetric(traceName, 'duration_ms', stopwatch.elapsedMilliseconds);
setMetric(traceName, 'hit', 1);
return result;
} catch (e) {
stopwatch.stop();
setMetric(traceName, 'miss', 1);
setAttribute(traceName, 'error_type', e.runtimeType.toString());
rethrow;
} finally {
await stopTrace(traceName);
}
}
// Custom operation timing
static void startOperation(String operationName) {
_operationStartTimes[operationName] = DateTime.now();
}
static Future<void> endOperation(String operationName, [Map<String, String>? attributes]) async {
final startTime = _operationStartTimes.remove(operationName);
if (startTime != null) {
final duration = DateTime.now().difference(startTime);
final traceName = 'operation_$operationName';
await startTrace(traceName);
setMetric(traceName, 'duration_ms', duration.inMilliseconds);
if (attributes != null) {
attributes.forEach((key, value) {
setAttribute(traceName, key, value);
});
}
await stopTrace(traceName);
}
}
// Cleanup
static Future<void> cleanup() async {
// Stop all active traces
final traceNames = List<String>.from(_activeTraces.keys);
for (final traceName in traceNames) {
await stopTrace(traceName);
}
_operationStartTimes.clear();
if (kDebugMode) {
print('Performance monitoring cleanup completed');
}
}
// Performance report
static Map<String, dynamic> getPerformanceReport() {
return {
'active_traces': _activeTraces.keys.toList(),
'pending_operations': _operationStartTimes.keys.toList(),
'timestamp': DateTime.now().toIso8601String(),
};
}
}
// Performance monitoring widget
class PerformanceWidget extends StatefulWidget {
final Widget child;
final String screenName;
const PerformanceWidget({
super.key,
required this.child,
required this.screenName,
});
@override
State<PerformanceWidget> createState() => _PerformanceWidgetState();
}
class _PerformanceWidgetState extends State<PerformanceWidget> with WidgetsBindingObserver {
late DateTime _screenLoadStart;
@override
void initState() {
super.initState();
_screenLoadStart = DateTime.now();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
final loadTime = DateTime.now().difference(_screenLoadStart);
PerformanceService.startTrace('screen_${widget.screenName}');
PerformanceService.setMetric('screen_${widget.screenName}', 'load_time_ms', loadTime.inMilliseconds);
PerformanceService.stopTrace('screen_${widget.screenName}');
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
Deployment Configuration
// deployment/build_config.dart - Build and deployment configuration
import 'package:flutter/foundation.dart';
class BuildConfig {
// Environment configuration
static const String environment = String.fromEnvironment('ENVIRONMENT', defaultValue: 'development');
static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL', defaultValue: 'http://localhost:3000');
static const String firebaseProjectId = String.fromEnvironment('FIREBASE_PROJECT_ID', defaultValue: 'flutter-demo-dev');
// Feature flags
static const bool enableAnalytics = bool.fromEnvironment('ENABLE_ANALYTICS', defaultValue: true);
static const bool enableCrashlytics = bool.fromEnvironment('ENABLE_CRASHLYTICS', defaultValue: true);
static const bool enablePerformanceMonitoring = bool.fromEnvironment('ENABLE_PERFORMANCE', defaultValue: true);
// Build configuration
static const String buildNumber = String.fromEnvironment('BUILD_NUMBER', defaultValue: '1');
static const String buildVersion = String.fromEnvironment('BUILD_VERSION', defaultValue: '1.0.0');
// Platform-specific configurations
static bool get isProduction => environment == 'production';
static bool get isDevelopment => environment == 'development';
static bool get isStaging => environment == 'staging';
// Debug configuration
static bool get showDebugInfo => kDebugMode && isDevelopment;
static bool get enableDebugPrint => kDebugMode || isStaging;
// Logging configuration
static bool get enableVerboseLogging => isDevelopment || isStaging;
static bool get enableNetworkLogging => kDebugMode;
// Security configuration
static bool get enableSSLPinning => isProduction;
static bool get allowTestingMode => !isProduction;
// Performance configuration
static int get networkTimeoutSeconds => isProduction ? 30 : 60;
static int get cacheTimeoutMinutes => isProduction ? 60 : 10;
}
// Environment-specific configurations
class EnvironmentConfig {
static Map<String, dynamic> get current {
switch (BuildConfig.environment) {
case 'production':
return productionConfig;
case 'staging':
return stagingConfig;
case 'development':
default:
return developmentConfig;
}
}
static const Map<String, dynamic> developmentConfig = {
'api_base_url': 'http://localhost:3000',
'enable_mock_data': true,
'log_level': 'verbose',
'cache_duration_minutes': 5,
'analytics_sample_rate': 0.1,
'performance_monitoring': true,
};
static const Map<String, dynamic> stagingConfig = {
'api_base_url': 'https://staging-api.example.com',
'enable_mock_data': false,
'log_level': 'info',
'cache_duration_minutes': 30,
'analytics_sample_rate': 0.5,
'performance_monitoring': true,
};
static const Map<String, dynamic> productionConfig = {
'api_base_url': 'https://api.example.com',
'enable_mock_data': false,
'log_level': 'error',
'cache_duration_minutes': 60,
'analytics_sample_rate': 1.0,
'performance_monitoring': true,
};
}
# pubspec.yaml - Dependencies and configuration
name: flutter_mobile_app
description: A comprehensive Flutter mobile application with all essential features
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.10.0"
dependencies:
flutter:
sdk: flutter
# Core dependencies
cupertino_icons: ^1.0.6
# Firebase services
firebase_core: ^2.20.0
firebase_auth: ^4.12.1
cloud_firestore: ^4.13.1
firebase_messaging: ^14.7.1
firebase_analytics: ^10.6.1
firebase_crashlytics: ^3.4.1
firebase_performance: ^0.9.3+1
firebase_storage: ^11.5.1
# Authentication
google_sign_in: ^6.1.5
sign_in_with_apple: ^5.0.0
crypto: ^3.0.3
# HTTP and networking
http: ^1.1.0
dio: ^5.3.2
connectivity_plus: ^5.0.1
# State management
provider: ^6.1.1
riverpod: ^2.4.6
flutter_riverpod: ^2.4.6
# Local storage
shared_preferences: ^2.2.2
hive: ^2.2.3
hive_flutter: ^1.1.0
sqflite: ^2.3.0
# UI and navigation
go_router: ^12.1.1
cached_network_image: ^3.3.0
flutter_svg: ^2.0.9
lottie: ^2.7.0
shimmer: ^3.0.0
# Notifications
flutter_local_notifications: ^16.2.0
timezone: ^0.9.2
# Utilities
intl: ^0.18.1
uuid: ^4.1.0
path_provider: ^2.1.1
image_picker: ^1.0.4
permission_handler: ^11.0.1
url_launcher: ^6.2.1
share_plus: ^7.2.1
device_info_plus: ^9.1.1
package_info_plus: ^4.2.0
# Development tools
logger: ^2.0.2+1
flutter_dotenv: ^5.1.0
dev_dependencies:
flutter_test:
sdk: flutter
# Build and code generation
build_runner: ^2.4.7
hive_generator: ^2.0.1
json_annotation: ^4.8.1
json_serializable: ^6.7.1
# Linting and formatting
flutter_lints: ^3.0.1
very_good_analysis: ^5.1.0
# Testing
mockito: ^5.4.2
integration_test:
sdk: flutter
# Flutter configuration
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
- assets/animations/
- assets/config/
- .env
fonts:
- family: CustomFont
fonts:
- asset: assets/fonts/CustomFont-Regular.ttf
- asset: assets/fonts/CustomFont-Bold.ttf
weight: 700
# Platform-specific configuration
flutter:
generate: true
# Build configuration for different environments
flavors:
development:
app_name: "Flutter App (Dev)"
bundle_id: "com.example.flutterapp.dev"
staging:
app_name: "Flutter App (Staging)"
bundle_id: "com.example.flutterapp.staging"
production:
app_name: "Flutter App"
bundle_id: "com.example.flutterapp"
#!/bin/bash
# build_scripts/build_ios.sh - iOS build script
set -e
# Configuration
ENVIRONMENT=${1:-production}
BUILD_NUMBER=${2:-$(date +%Y%m%d%H%M)}
BUILD_VERSION=${3:-"1.0.0"}
echo "Building iOS app for environment: $ENVIRONMENT"
echo "Build number: $BUILD_NUMBER"
echo "Build version: $BUILD_VERSION"
# Clean previous builds
flutter clean
flutter pub get
# Generate necessary files
flutter packages pub run build_runner build --delete-conflicting-outputs
# Set build configuration
export ENVIRONMENT=$ENVIRONMENT
export BUILD_NUMBER=$BUILD_NUMBER
export BUILD_VERSION=$BUILD_VERSION
# Build based on environment
case $ENVIRONMENT in
"development")
echo "Building for development..."
flutter build ios --debug --flavor development --dart-define=ENVIRONMENT=development
;;
"staging")
echo "Building for staging..."
flutter build ios --release --flavor staging --dart-define=ENVIRONMENT=staging
;;
"production")
echo "Building for production..."
flutter build ios --release --flavor production --dart-define=ENVIRONMENT=production
;;
*)
echo "Unknown environment: $ENVIRONMENT"
exit 1
;;
esac
echo "iOS build completed successfully!"
# Optional: Archive and export
if [ "$ENVIRONMENT" = "production" ]; then
echo "Creating archive for App Store distribution..."
xcodebuild -workspace ios/Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-destination generic/platform=iOS \
-archivePath build/ios/Runner.xcarchive \
archive
echo "Exporting IPA for App Store distribution..."
xcodebuild -exportArchive \
-archivePath build/ios/Runner.xcarchive \
-exportPath build/ios/ipa \
-exportOptionsPlist ios/ExportOptions.plist
fi
#!/bin/bash
# build_scripts/build_android.sh - Android build script
set -e
# Configuration
ENVIRONMENT=${1:-production}
BUILD_NUMBER=${2:-$(date +%Y%m%d%H%M)}
BUILD_VERSION=${3:-"1.0.0"}
echo "Building Android app for environment: $ENVIRONMENT"
echo "Build number: $BUILD_NUMBER"
echo "Build version: $BUILD_VERSION"
# Clean previous builds
flutter clean
flutter pub get
# Generate necessary files
flutter packages pub run build_runner build --delete-conflicting-outputs
# Set build configuration
export ENVIRONMENT=$ENVIRONMENT
export BUILD_NUMBER=$BUILD_NUMBER
export BUILD_VERSION=$BUILD_VERSION
# Build based on environment
case $ENVIRONMENT in
"development")
echo "Building APK for development..."
flutter build apk --debug --flavor development --dart-define=ENVIRONMENT=development
;;
"staging")
echo "Building APK for staging..."
flutter build apk --release --flavor staging --dart-define=ENVIRONMENT=staging
;;
"production")
echo "Building App Bundle for production..."
flutter build appbundle --release --flavor production --dart-define=ENVIRONMENT=production
;;
*)
echo "Unknown environment: $ENVIRONMENT"
exit 1
;;
esac
echo "Android build completed successfully!"
# Copy built files to deployment directory
DEPLOY_DIR="build/deploy/${ENVIRONMENT}"
mkdir -p $DEPLOY_DIR
if [ "$ENVIRONMENT" = "production" ]; then
cp build/app/outputs/bundle/productionRelease/app-production-release.aab $DEPLOY_DIR/
echo "App Bundle copied to: $DEPLOY_DIR"
else
cp build/app/outputs/flutter-apk/app-$ENVIRONMENT-*.apk $DEPLOY_DIR/
echo "APK copied to: $DEPLOY_DIR"
fi
# .github/workflows/flutter_ci_cd.yml - CI/CD workflow
name: Flutter CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Run code generation
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Run tests
run: flutter test --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: coverage/lcov.info
build_android:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Install dependencies
run: flutter pub get
- name: Build Android App Bundle
run: |
flutter build appbundle --release \
--dart-define=ENVIRONMENT=production \
--build-number=${{ github.run_number }}
- name: Upload Android artifacts
uses: actions/upload-artifact@v3
with:
name: android-release
path: build/app/outputs/bundle/release/
build_ios:
needs: test
runs-on: macos-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Build iOS
run: |
flutter build ios --release --no-codesign \
--dart-define=ENVIRONMENT=production \
--build-number=${{ github.run_number }}
- name: Upload iOS artifacts
uses: actions/upload-artifact@v3
with:
name: ios-release
path: build/ios/iphoneos/
Todo リスト更新
<function_calls>