Dart Development Mastery
Modern Dart Development with 2025 Best Practices
Comprehensive Dart development guidance covering Flutter mobile applications, async programming patterns, server-side development, and cross-platform solutions using the latest tools and frameworks.
What It Does
Flutter Mobile Development
- Mobile App Development: Flutter 3.x with Material Design 3 and Cupertino widgets
- State Management: Provider, Riverpod, BLoC patterns for scalable applications
- Navigation: Go Router for declarative routing and deep linking
- Performance: Widget optimization, lazy loading, memory management
Server-Side Development
- Web APIs: Shelf, Dart Frog, or Aqueduct for backend services
- Database Integration: PostgreSQL, MongoDB with async drivers
- Real-time Communication: WebSockets, gRPC with Dart's async capabilities
- Testing: Unit tests, widget tests, integration tests with Dart test framework
Cross-Platform Development
- Flutter for Multiple Platforms: iOS, Android, Web, Desktop, and Embedded
- Shared Codebases: Dart packages for business logic across platforms
- Platform-Specific APIs: Method channels for native integration
When to Use
Perfect Scenarios
- Building cross-platform mobile applications with Flutter
- Developing scalable Flutter apps with modern state management
- Creating server-side APIs with Dart
- Implementing real-time applications with WebSockets
- Building web applications with Flutter Web
- Developing desktop applications with Flutter Desktop
- Creating embedded systems applications
Common Triggers
- "Create Flutter app"
- "Build Dart web API"
- "Implement Flutter state management"
- "Optimize Flutter performance"
- "Test Flutter application"
- "Dart best practices"
Tool Version Matrix (2025-11-06)
Core Dart/Flutter
- Dart: 3.5.x (current) / 3.4.x (LTS)
- Flutter: 3.24.x (current) / 3.22.x (LTS)
- Dart SDK: 3.5.0
- Package Manager: pub (built-in)
Flutter Frameworks
- Material Design: 3.x (Material 3)
- Cupertino Widgets: iOS 17+ support
- Go Router: 13.x - Declarative routing
- Riverpod: 2.5.x - Reactive state management
- BLoC: 8.1.x - State management library
Testing Tools
- Dart Test: Built-in testing framework
- Flutter Test: Widget and integration testing
- Mockito: 5.4.x - Mocking framework
- Golden Tests: Widget screenshot testing
- Integration Test: End-to-end testing
Development Tools
- Flutter CLI: 3.24.x
- Dart DevTools: Web-based debugging tools
- Android Studio: Dolphin 2024.x
- VS Code: Flutter extension
Backend Tools
- Shelf: 1.4.x - Web server framework
- Dart Frog: 1.0.x - Server-side framework
- Aqueduct: 7.x - Full-stack framework
- MongoDB Dart Driver: 4.12.x
Ecosystem Overview
Package Management
# Create new Flutter project
flutter create my_app
flutter create --org com.example --platforms=web,desktop my_app
# Create new Dart project
dart create my_dart_app
# Add dependencies
flutter pub add provider riverpod go_router
flutter pub add dio retrofit json_annotation
dart pub add shelf shelf_router
# Get dependencies
flutter pub get
dart pub get
# Run and build
flutter run
flutter run --release
flutter build apk
flutter build web
dart run
Project Structure (2025 Best Practice)
my_flutter_app/
├── lib/
│ ├── main.dart # App entry point
│ ├── app.dart # App configuration
│ ├── core/ # Core utilities
│ │ ├── constants/ # App constants
│ │ ├── errors/ # Custom error classes
│ │ ├── extensions/ # Dart extensions
│ │ ├── network/ # Network configuration
│ │ ├── themes/ # App themes
│ │ └── utils/ # Utility functions
│ ├── features/ # Feature modules
│ │ ├── authentication/ # Auth feature
│ │ │ ├── data/ # Data layer (repositories, data sources)
│ │ │ ├── domain/ # Domain layer (entities, use cases)
│ │ │ └── presentation/ # UI layer (pages, widgets, providers)
│ │ ├── user_profile/ # User profile feature
│ │ └── settings/ # Settings feature
│ ├── shared/ # Shared components
│ │ ├── widgets/ # Reusable widgets
│ │ ├── models/ # Shared data models
│ │ ├── services/ # Shared services
│ │ └── providers/ # Shared providers
│ └── routes/ # App routes
├── test/ # Test files
│ ├── unit/ # Unit tests
│ ├── widget/ # Widget tests
│ └── integration/ # Integration tests
├── assets/ # Static assets
├── pubspec.yaml # Dependencies
└── analysis_options.yaml # Linting rules
Modern Development Patterns
Dart 3.x Language Features
// Enhanced patterns with records and pattern matching
sealed class NetworkResult<T> {
const NetworkResult();
}
class Success<T> extends NetworkResult<T> {
final T data;
const Success(this.data);
}
class Error<T> extends NetworkResult<T> {
final String message;
final Exception? exception;
const Error(this.message, [this.exception]);
}
class Loading<T> extends NetworkResult<T> {
const Loading();
}
// Pattern matching with switch expressions
T handleNetworkResult<T>(NetworkResult<T> result) {
return switch (result) {
Success(data: final data) => data,
Error(message: final message) => throw Exception(message),
Loading() => throw StateError('Still loading'),
};
}
// Records for lightweight data structures
typedef UserInfo = (String name, int age, String email);
class UserService {
UserInfo getUserInfo(int id) {
return ('John Doe', 30, 'john@example.com');
}
void printUserInfo(UserInfo user) {
final (name, age, email) = user;
print('$name, $age years old, email: $email');
}
}
// Enhanced type inference and const constructors
class AppConfig {
static const apiBaseUrl = String.fromEnvironment('API_BASE_URL', defaultValue: 'https://api.example.com');
static const appVersion = String.fromEnvironment('APP_VERSION', defaultValue: '1.0.0');
static const isDebug = bool.fromEnvironment('DEBUG', defaultValue: false);
}
// Enhanced enums with methods and properties
enum ThemeMode {
light._('Light Theme', '☀️'),
dark._('Dark Theme', '🌙'),
system._('System Theme', '💻');
const ThemeMode._(this.displayName, this.icon);
final String displayName;
final String icon;
Brightness get brightness => switch (this) {
ThemeMode.light => Brightness.light,
ThemeMode.dark => Brightness.dark,
ThemeMode.system => PlatformDispatcher.instance.platformBrightness,
};
}
// Extension methods for enhanced APIs
extension StringExtension on String {
bool get isValidEmail {
return RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(this);
}
String get capitalize {
return '${this[0].toUpperCase()}${substring(1)}';
}
String truncate(int length, {String suffix = '...'}) {
if (this.length <= length) return this;
return '${substring(0, length)}$suffix';
}
}
Modern Flutter State Management with Riverpod
// Provider setup with Riverpod 2.x
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Model classes with immutability
@immutable
class User {
final String id;
final String name;
final String email;
final String avatarUrl;
const User({
required this.id,
required this.name,
required this.email,
required this.avatarUrl,
});
User copyWith({
String? id,
String? name,
String? email,
String? avatarUrl,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
avatarUrl: avatarUrl ?? this.avatarUrl,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User &&
other.id == id &&
other.name == name &&
other.email == email;
}
@override
int get hashCode => id.hashCode ^ name.hashCode ^ email.hashCode;
}
// Repository interface
abstract class UserRepository {
Future<User> getUser(String userId);
Future<List<User>> getUsers({int page = 1, int limit = 20});
Future<User> updateUser(User user);
Future<void> deleteUser(String userId);
}
// Implementation with HTTP client
class HttpUserRepository implements UserRepository {
final Dio _dio;
HttpUserRepository(this._dio);
@override
Future<User> getUser(String userId) async {
try {
final response = await _dio.get('/users/$userId');
return User.fromJson(response.data);
} on DioException catch (e) {
throw UserRepositoryException('Failed to get user: $e');
}
}
@override
Future<List<User>> getUsers({int page = 1, int limit = 20}) async {
try {
final response = await _dio.get('/users', queryParameters: {
'page': page,
'limit': limit,
});
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
} on DioException catch (e) {
throw UserRepositoryException('Failed to get users: $e');
}
}
@override
Future<User> updateUser(User user) async {
try {
final response = await _dio.put('/users/${user.id}', data: user.toJson());
return User.fromJson(response.data);
} on DioException catch (e) {
throw UserRepositoryException('Failed to update user: $e');
}
}
@override
Future<void> deleteUser(String userId) async {
try {
await _dio.delete('/users/$userId');
} on DioException catch (e) {
throw UserRepositoryException('Failed to delete user: $e');
}
}
}
// Riverpod providers
final dioProvider = Provider<Dio>((ref) {
final dio = Dio(BaseOptions(baseUrl: AppConfig.apiBaseUrl));
dio.interceptors.add(LogInterceptor());
return dio;
});
final userRepositoryProvider = Provider<UserRepository>((ref) {
return HttpUserRepository(ref.read(dioProvider));
});
// Async providers for data fetching
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
final repository = ref.watch(userRepositoryProvider);
return repository.getUser(userId);
});
final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(UsersNotifier.new);
// Notifier for managing state
class UsersNotifier extends AsyncNotifier<List<User>> {
int _page = 1;
final int _limit = 20;
bool _hasMore = true;
@override
Future<List<User>> build() async {
return _loadUsers();
}
Future<List<User>> _loadUsers() async {
if (state.isLoading || !_hasMore) return state.value ?? [];
state = const AsyncLoading();
try {
final repository = ref.read(userRepositoryProvider);
final newUsers = await repository.getUsers(page: _page, limit: _limit);
if (newUsers.length < _limit) {
_hasMore = false;
}
final currentUsers = state.value ?? [];
final updatedUsers = _page == 1 ? newUsers : [...currentUsers, ...newUsers];
state = AsyncData(updatedUsers);
_page++;
return updatedUsers;
} catch (error, stackTrace) {
state = AsyncError(error, stackTrace);
rethrow;
}
}
Future<void> loadMoreUsers() async {
await _loadUsers();
}
Future<void> refresh() async {
_page = 1;
_hasMore = true;
await _loadUsers();
}
}
// State management with Notifier for single user
final userNotifierProvider = AsyncNotifierProvider.family<UserNotifier, User, String>(UserNotifier.new);
class UserNotifier extends FamilyAsyncNotifier<User, String> {
@override
Future<User> build(String arg) async {
final repository = ref.read(userRepositoryProvider);
return repository.getUser(arg);
}
Future<void> updateUser(User user) async {
state = const AsyncLoading();
try {
final repository = ref.read(userRepositoryProvider);
final updatedUser = await repository.updateUser(user);
state = AsyncData(updatedUser);
} catch (error, stackTrace) {
state = AsyncError(error, stackTrace);
}
}
}
// App-wide state providers
final appThemeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);
final appLocaleProvider = StateProvider<Locale>((ref) => const Locale('en', 'US'));
Modern Flutter UI with Material 3
// Modern app with Material 3 and go_router
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appRouter = ref.watch(goRouterProvider);
final themeMode = ref.watch(appThemeProvider);
final appLocale = ref.watch(appLocaleProvider);
return MaterialApp.router(
title: 'My Flutter App',
debugShowCheckedModeBanner: false,
themeMode: themeMode,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
locale: appLocale,
supportedLocales: const [
Locale('en', 'US'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
routerConfig: appRouter,
);
}
}
// Modern theme configuration
class AppTheme {
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.light,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
scrolledUnderElevation: 1,
),
cardTheme: CardTheme(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
}
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.dark,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
scrolledUnderElevation: 1,
),
cardTheme: CardTheme(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
);
}
}
// Modern user list widget with state management
class UserListScreen extends ConsumerWidget {
const UserListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.read(usersProvider.notifier).refresh();
},
),
],
),
body: RefreshIndicator(
onRefresh: () async {
await ref.read(usersProvider.notifier).refresh();
},
child: usersAsync.when(
data: (users) {
if (users.isEmpty) {
return const EmptyStateWidget(
message: 'No users found',
icon: Icons.people_outline,
);
}
return NotificationListener<ScrollNotification>(
onNotification: (scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
ref.read(usersProvider.notifier).loadMoreUsers();
}
return false;
},
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: users.length + 1, // +1 for loading indicator
itemBuilder: (context, index) {
if (index == users.length) {
return const LoadingIndicator();
}
return UserCard(user: users[index]);
},
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorWidget(
error: error,
onRetry: () {
ref.read(usersProvider.notifier).refresh();
},
),
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
context.go('/add-user');
},
icon: const Icon(Icons.add),
label: const Text('Add User'),
),
);
}
}
// Modern user card widget
class UserCard extends ConsumerWidget {
final User user;
const UserCard({
super.key,
required this.user,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () {
context.go('/users/${user.id}');
},
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundImage: NetworkImage(user.avatarUrl),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
child: user.avatarUrl.isEmpty
? Text(
user.name.isNotEmpty ? user.name[0].toUpperCase() : '?',
style: Theme.of(context).textTheme.titleLarge,
)
: null,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.name,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
user.email,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
_showUserMenu(context, ref, user);
},
),
],
),
),
),
);
}
void _showUserMenu(BuildContext context, WidgetRef ref, User user) {
showModalBottomSheet(
context: context,
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.edit),
title: const Text('Edit User'),
onTap: () {
Navigator.pop(context);
context.go('/users/${user.id}/edit');
},
),
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
title: const Text('Delete User', style: TextStyle(color: Colors.red)),
onTap: () async {
Navigator.pop(context);
final confirmed = await _showDeleteConfirmation(context);
if (confirmed) {
try {
await ref.read(userRepositoryProvider).deleteUser(user.id);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('User deleted successfully')),
);
}
} catch (error) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete user: $error')),
);
}
}
}
},
),
],
),
);
},
);
}
Future<bool> _showDeleteConfirmation(BuildContext context) async {
return await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete User'),
content: Text('Are you sure you want to delete ${user.name}?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
);
},
) ?? false;
}
}
Go Router for Navigation
// Go router configuration
final goRouterProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: '/',
debugLogDiagnostics: AppConfig.isDebug,
routes: [
// Shell route for navigation
ShellRoute(
builder: (context, state, child) {
return MainScaffold(child: child);
},
routes: [
// Home route
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
// User routes
GoRoute(
path: '/users',
builder: (context, state) => const UserListScreen(),
routes: [
GoRoute(
path: '/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return UserDetailScreen(userId: userId);
},
routes: [
GoRoute(
path: '/edit',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return EditUserScreen(userId: userId);
},
),
],
),
],
),
// Settings route
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileSettingsScreen(),
),
GoRoute(
path: '/appearance',
builder: (context, state) => const AppearanceSettingsScreen(),
),
],
),
],
),
// Standalone routes
GoRoute(
path: '/add-user',
builder: (context, state) => const AddUserScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
// Error handling
errorBuilder: (context, state) => ErrorScreen(error: state.error),
// Redirects
redirect: (context, state) {
// Example: redirect to login if not authenticated
final isAuthenticated = true; // Check authentication status
if (!isAuthenticated && !state.location.startsWith('/login')) {
return '/login';
}
return null;
},
);
});
// Main scaffold with bottom navigation
class MainScaffold extends ConsumerWidget {
const MainScaffold({
required this.child,
super.key,
});
final Widget child;
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedIndex = ref.watch(bottomNavigationIndexProvider);
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
ref.read(bottomNavigationIndexProvider.notifier).state = index;
switch (index) {
case 0:
context.go('/');
break;
case 1:
context.go('/users');
break;
case 2:
context.go('/settings');
break;
}
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.people_outlined),
selectedIcon: Icon(Icons.people),
label: 'Users',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
}
// Provider for bottom navigation state
final bottomNavigationIndexProvider = StateProvider<int>((ref) => 0);
Performance Considerations
Widget Performance
// Performance-optimized widgets with const constructors
class OptimizedUserCard extends StatelessWidget {
final User user;
final VoidCallback? onTap;
final VoidCallback? onEdit;
final VoidCallback? onDelete;
const OptimizedUserCard({
super.key,
required this.user,
this.onTap,
this.onEdit,
this.onDelete,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
// Use Hero widget for smooth avatar transitions
Hero(
tag: 'user-avatar-${user.id}',
child: UserAvatar(
imageUrl: user.avatarUrl,
name: user.name,
size: 48,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildUserInfo(),
),
_buildActionButtons(),
],
),
),
),
);
}
Widget _buildUserInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
user.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
user.email,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
);
}
Widget _buildActionButtons() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (onEdit != null)
IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: onEdit,
visualDensity: VisualDensity.compact,
),
if (onDelete != null)
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: onDelete,
visualDensity: VisualDensity.compact,
),
],
);
}
}
// Efficient image loading and caching
class CachedNetworkImage extends StatefulWidget {
final String imageUrl;
final double? width;
final double? height;
final Widget? placeholder;
final Widget? errorWidget;
final BoxFit fit;
const CachedNetworkImage({
super.key,
required this.imageUrl,
this.width,
this.height,
this.placeholder,
this.errorWidget,
this.fit = BoxFit.cover,
});
@override
State<CachedNetworkImage> createState() => _CachedNetworkImageState();
}
class _CachedNetworkImageState extends State<CachedNetworkImage> {
final Map<String, ui.Image> _imageCache = {};
bool _isLoading = true;
bool _hasError = false;
@override
void initState() {
super.initState();
_loadImage();
}
Future<void> _loadImage() async {
if (_imageCache.containsKey(widget.imageUrl)) {
if (mounted) {
setState(() {
_isLoading = false;
});
}
return;
}
try {
final image = await _fetchImage();
_imageCache[widget.imageUrl] = image;
if (mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_hasError = true;
});
}
}
}
Future<ui.Image> _fetchImage() async {
final completer = Completer<ui.Image>();
final codec = await ui.instantiateImageCodec(
await NetworkAssetBundle(Uri.parse(widget.imageUrl)).load(widget.imageUrl).then((bytes) => bytes.buffer.asUint8List()),
);
final frame = await codec.getNextFrame();
completer.complete(frame.image);
return completer.future;
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return widget.placeholder ??
Container(
width: widget.width,
height: widget.height,
color: Colors.grey[200],
child: const Center(
child: CircularProgressIndicator(),
),
);
}
if (_hasError) {
return widget.errorWidget ??
Container(
width: widget.width,
height: widget.height,
color: Colors.grey[200],
child: const Icon(Icons.error),
);
}
final image = _imageCache[widget.imageUrl];
return CustomPaint(
size: Size(widget.width ?? double.infinity, widget.height ?? double.infinity),
painter: _ImagePainter(image: image!, fit: widget.fit),
);
}
}
class _ImagePainter extends CustomPainter {
final ui.Image image;
final BoxFit fit;
_ImagePainter({required this.image, required this.fit});
@override
void paint(Canvas canvas, Size size) {
final imageSize = Size(image.width.toDouble(), image.height.toDouble());
final scales = _calculateScales(imageSize, size);
final paint = Paint()
..isAntiAlias = true
..filterQuality = FilterQuality.high;
canvas.save();
if (fit == BoxFit.cover) {
canvas.scale(scales.dx, scales.dy);
canvas.drawImageRect(
image,
Rect.fromLTWH(0, 0, imageSize.width, imageSize.height),
Rect.fromLTWH(0, 0, size.width / scales.dx, size.height / scales.dy),
paint,
);
}
canvas.restore();
}
Offset _calculateScales(Size inputSize, Size outputSize) {
final scaleX = outputSize.width / inputSize.width;
final scaleY = outputSize.height / inputSize.height;
return Offset(scaleX, scaleY);
}
@override
bool shouldRepaint(covariant _ImagePainter oldDelegate) {
return image != oldDelegate.image || fit != oldDelegate.fit;
}
}
// ListView with lazy loading and recycling
class OptimizedListView<T> extends StatelessWidget {
final List<T> items;
final Widget Function(BuildContext context, T item, int index) itemBuilder;
final VoidCallback? onLoadMore;
final bool hasMore;
final bool isLoading;
const OptimizedListView({
super.key,
required this.items,
required this.itemBuilder,
this.onLoadMore,
this.hasMore = false,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification &&
notification.metrics.extentAfter == 0 &&
hasMore &&
!isLoading &&
onLoadMore != null) {
onLoadMore!();
}
return false;
},
child: ListView.builder(
itemCount: items.length + (hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == items.length) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
);
}
return itemBuilder(context, items[index], index);
},
),
);
}
}
Memory Management
// Efficient memory usage with ImageCache
class ImageCacheManager {
static final ImageCacheManager _instance = ImageCacheManager._internal();
factory ImageCacheManager() => _instance;
ImageCacheManager._internal();
final PaintingBinding _paintingBinding = PaintingBinding.instance;
final Map<String, ui.Image> _memoryCache = {};
final int _maxCacheSize = 100 * 1024 * 1024; // 100MB
int _currentCacheSize = 0;
Future<ui.Image?> getImage(String url) async {
// Check memory cache first
if (_memoryCache.containsKey(url)) {
return _memoryCache[url];
}
// Check painting binding cache
final cachedImage = _paintingBinding.imageCache?.image;
if (cachedImage != null) {
return cachedImage;
}
try {
final image = await _loadImage(url);
_addToMemoryCache(url, image);
return image;
} catch (e) {
return null;
}
}
Future<ui.Image> _loadImage(String url) async {
final completer = Completer<ui.Image>();
final codec = await ui.instantiateImageCodec(
await _fetchImageData(url),
);
final frame = await codec.getNextFrame();
completer.complete(frame.image);
return completer.future;
}
Future<Uint8List> _fetchImageData(String url) async {
final response = await http.get(Uri.parse(url));
return response.bodyBytes;
}
void _addToMemoryCache(String url, ui.Image image) {
final imageSize = image.width * image.height * 4; // 4 bytes per pixel
if (_currentCacheSize + imageSize > _maxCacheSize) {
_evictLeastRecentlyUsed(imageSize);
}
_memoryCache[url] = image;
_currentCacheSize += imageSize;
}
void _evictLeastRecentlyUsed(int requiredSize) {
final entries = _memoryCache.entries.toList();
entries.sort((a, b) => a.key.compareTo(b.key));
int freedSize = 0;
for (final entry in entries) {
final imageSize = entry.value.width * entry.value.height * 4;
_memoryCache.remove(entry.key);
freedSize += imageSize;
_currentCacheSize -= imageSize;
if (freedSize >= requiredSize) {
break;
}
}
}
void clearCache() {
_memoryCache.clear();
_currentCacheSize = 0;
_paintingBinding.imageCache?.clear();
_paintingBinding.imageCache?.clearLiveImages();
}
}
// Resource management with automatic cleanup
class ResourceManager {
final Map<String, StreamSubscription> _subscriptions = {};
final Map<String, Timer> _timers = {};
StreamSubscription<T>? addSubscription<T>(
String key,
StreamSubscription<T> subscription,
) {
_subscriptions[key] = subscription as StreamSubscription;
return subscription;
}
Timer? addTimer(String key, Duration duration, VoidCallback callback) {
final timer = Timer(duration, callback);
_timers[key] = timer;
return timer;
}
void removeSubscription(String key) {
final subscription = _subscriptions.remove(key);
subscription?.cancel();
}
void removeTimer(String key) {
final timer = _timers.remove(key);
timer?.cancel();
}
void dispose() {
for (final subscription in _subscriptions.values) {
subscription.cancel();
}
_subscriptions.clear();
for (final timer in _timers.values) {
timer.cancel();
}
_timers.clear();
}
}
// Widget with automatic resource cleanup
class AutoCleanupWidget extends StatefulWidget {
final Widget child;
final VoidCallback? onInit;
final VoidCallback? onDispose;
const AutoCleanupWidget({
super.key,
required this.child,
this.onInit,
this.onDispose,
});
@override
State<AutoCleanupWidget> createState() => _AutoCleanupWidgetState();
}
class _AutoCleanupWidgetState extends State<AutoCleanupWidget> {
final ResourceManager _resourceManager = ResourceManager();
@override
void initState() {
super.initState();
widget.onInit?.call();
}
@override
void dispose() {
_resourceManager.dispose();
widget.onDispose?.call();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
Testing Strategy
Unit Testing with Dart Test Framework
// Unit tests for business logic
void main() {
group('UserRepository', () {
late UserRepository userRepository;
late MockDio mockDio;
setUp(() {
mockDio = MockDio();
userRepository = HttpUserRepository(mockDio);
});
test('should return user when getUser is called with valid ID', () async {
// Arrange
final userId = '123';
final userJson = {
'id': userId,
'name': 'John Doe',
'email': 'john@example.com',
'avatarUrl': 'https://example.com/avatar.jpg',
};
when(() => mockDio.get('/users/$userId'))
.thenAnswer((_) async => Response(data: userJson, statusCode: 200));
// Act
final result = await userRepository.getUser(userId);
// Assert
expect(result.id, equals(userId));
expect(result.name, equals('John Doe'));
expect(result.email, equals('john@example.com'));
expect(result.avatarUrl, equals('https://example.com/avatar.jpg'));
verify(() => mockDio.get('/users/$userId')).called(1);
});
test('should throw UserRepositoryException when API call fails', () async {
// Arrange
final userId = '123';
when(() => mockDio.get('/users/$userId'))
.thenThrow(DioException(requestOptions: RequestOptions(path: '/users/$userId')));
// Act & Assert
expect(
() => userRepository.getUser(userId),
throwsA(isA<UserRepositoryException>()),
);
verify(() => mockDio.get('/users/$userId')).called(1);
});
test('should return users list when getUsers is called', () async {
// Arrange
final usersJson = [
{
'id': '1',
'name': 'John Doe',
'email': 'john@example.com',
'avatarUrl': 'https://example.com/avatar1.jpg',
},
{
'id': '2',
'name': 'Jane Smith',
'email': 'jane@example.com',
'avatarUrl': 'https://example.com/avatar2.jpg',
},
];
when(() => mockDio.get('/users', queryParameters: any(named:
…(truncated)