skills/moai-lang-flutter/references/reference.md

687 lines
16 KiB
Markdown

# Flutter/Dart Reference Guide
## Platform Version Matrix
### Dart 3.5 (November 2025)
- Release: November 2025
- Key Features:
- Pattern matching with exhaustiveness checking
- Records and destructuring
- Sealed classes and interfaces
- Extension types with zero runtime cost
- Macros (experimental)
- Enhanced class modifiers (sealed, final, base, interface)
### Flutter 3.24 (November 2025)
- Release: November 2025
- Minimum OS: iOS 12.0+, Android API 21+
- Key Features:
- Material 3 design system
- Impeller rendering engine (default on iOS)
- Adaptive layouts and responsive design
- Enhanced platform views
- Improved hot reload and DevTools
## Context7 Library Mappings
### Core Framework
```
/flutter/flutter - Flutter framework and engine
/dart-lang/sdk - Dart language SDK
/flutter/packages - Official Flutter packages (go_router, etc.)
```
### State Management
```
/rrousselGit/riverpod - Riverpod state management
/felangel/bloc - BLoC pattern library
/jonataslaw/getx - GetX framework
/alibaba/flutter_redux - Redux for Flutter
```
### Navigation
```
/flutter/packages - go_router official package
/theyakka/fluro - Fluro router
```
### Networking
```
/cfug/dio - Dio HTTP client
/nickmeinhold/chopper - Chopper HTTP client generator
```
### Storage
```
/isar/isar - Isar NoSQL database
/simonbengtsson/drift - Drift SQL database
/nickmeinhold/hive - Hive key-value storage
/nickmeinhold/shared_preferences - SharedPreferences wrapper
```
### UI Components
```
/Sub6Resources/flutter_html - HTML rendering
/nickmeinhold/cached_network_image - Image caching
/nickmeinhold/flutter_svg - SVG rendering
```
### Testing
```
/flutter/flutter - flutter_test (built-in)
/dart-lang/mockito - Mockito for Dart
/nickmeinhold/mocktail - Mocktail testing
```
## Architecture Patterns
### Clean Architecture with Riverpod
Layer Organization:
```dart
// lib/
// ├── core/
// │ ├── error/
// │ │ ├── exceptions.dart
// │ │ └── failures.dart
// │ ├── network/
// │ │ └── api_client.dart
// │ └── utils/
// │ └── extensions.dart
// ├── features/
// │ └── user/
// │ ├── data/
// │ │ ├── datasources/
// │ │ │ ├── user_local_datasource.dart
// │ │ │ └── user_remote_datasource.dart
// │ │ ├── models/
// │ │ │ └── user_model.dart
// │ │ └── repositories/
// │ │ └── user_repository_impl.dart
// │ ├── domain/
// │ │ ├── entities/
// │ │ │ └── user.dart
// │ │ ├── repositories/
// │ │ │ └── user_repository.dart
// │ │ └── usecases/
// │ │ ├── get_user.dart
// │ │ └── update_user.dart
// │ └── presentation/
// │ ├── providers/
// │ │ └── user_provider.dart
// │ ├── screens/
// │ │ └── user_screen.dart
// │ └── widgets/
// │ └── user_card.dart
// └── main.dart
// Domain Layer - Entity
class User {
final String id;
final String name;
final String email;
final DateTime? createdAt;
const User({
required this.id,
required this.name,
required this.email,
this.createdAt,
});
}
// Domain Layer - Repository Interface
abstract class UserRepository {
Future<User> getUser(String id);
Future<User> updateUser(User user);
Stream<User> watchUser(String id);
}
// Domain Layer - Use Case
class GetUserUseCase {
final UserRepository _repository;
const GetUserUseCase(this._repository);
Future<User> call(String id) => _repository.getUser(id);
}
// Data Layer - Model
class UserModel extends User {
const UserModel({
required super.id,
required super.name,
required super.email,
super.createdAt,
});
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: null,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
if (createdAt != null) 'created_at': createdAt!.toIso8601String(),
};
}
// Data Layer - Repository Implementation
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource _remote;
final UserLocalDataSource _local;
UserRepositoryImpl(this._remote, this._local);
@override
Future<User> getUser(String id) async {
try {
final user = await _remote.fetchUser(id);
await _local.cacheUser(user);
return user;
} on NetworkException {
final cached = await _local.getCachedUser(id);
if (cached != null) return cached;
rethrow;
}
}
@override
Future<User> updateUser(User user) async {
final updated = await _remote.updateUser(user as UserModel);
await _local.cacheUser(updated);
return updated;
}
@override
Stream<User> watchUser(String id) => _local.watchUser(id);
}
// Presentation Layer - Provider
@riverpod
UserRepository userRepository(Ref ref) {
return UserRepositoryImpl(
ref.read(userRemoteDataSourceProvider),
ref.read(userLocalDataSourceProvider),
);
}
@riverpod
class UserController extends _$UserController {
@override
FutureOr<User?> build(String userId) async {
return ref.read(userRepositoryProvider).getUser(userId);
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(userRepositoryProvider).getUser(arg),
);
}
Future<void> update(User user) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(userRepositoryProvider).updateUser(user),
);
}
}
```
## Concurrency Patterns
### Isolates for Heavy Computation
Simple Compute:
```dart
// Using compute for one-off operations
Future<List<ProcessedItem>> processItems(List<RawItem> items) async {
return compute(_processItemsIsolate, items);
}
List<ProcessedItem> _processItemsIsolate(List<RawItem> items) {
return items.map((item) => ProcessedItem.from(item)).toList();
}
```
Long-Running Isolate:
```dart
class ImageProcessor {
late final Isolate _isolate;
late final ReceivePort _receivePort;
late final SendPort _sendPort;
bool _isInitialized = false;
Future<void> spawn() async {
if (_isInitialized) return;
_receivePort = ReceivePort();
_isolate = await Isolate.spawn(
_isolateEntry,
_receivePort.sendPort,
);
_sendPort = await _receivePort.first as SendPort;
_isInitialized = true;
}
Future<Uint8List> processImage(Uint8List imageData) async {
if (!_isInitialized) {
throw StateError('ImageProcessor not initialized. Call spawn() first.');
}
final responsePort = ReceivePort();
_sendPort.send(_ProcessImageRequest(imageData, responsePort.sendPort));
final result = await responsePort.first;
if (result is _ProcessImageError) {
throw result.error;
}
return result as Uint8List;
}
void dispose() {
_isolate.kill(priority: Isolate.immediate);
_receivePort.close();
}
static void _isolateEntry(SendPort sendPort) {
final receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
if (message is _ProcessImageRequest) {
try {
final processed = _heavyImageProcessing(message.imageData);
message.replyPort.send(processed);
} catch (e) {
message.replyPort.send(_ProcessImageError(e));
}
}
});
}
static Uint8List _heavyImageProcessing(Uint8List data) {
// Heavy computation here
return data;
}
}
class _ProcessImageRequest {
final Uint8List imageData;
final SendPort replyPort;
_ProcessImageRequest(this.imageData, this.replyPort);
}
class _ProcessImageError {
final Object error;
_ProcessImageError(this.error);
}
```
## Navigation Patterns
### Advanced go_router Configuration
Nested Navigation:
```dart
final router = GoRouter(
initialLocation: '/',
routes: [
// Root route
GoRoute(
path: '/',
redirect: (context, state) => '/home',
),
// Main app with bottom navigation
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return MainScaffold(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/home',
name: 'home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'detail/:id',
name: 'home-detail',
builder: (context, state) => DetailScreen(
id: state.pathParameters['id']!,
),
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/search',
name: 'search',
builder: (context, state) => const SearchScreen(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/profile',
name: 'profile',
builder: (context, state) => const ProfileScreen(),
),
],
),
],
),
// Auth routes (outside main scaffold)
GoRoute(
path: '/login',
name: 'login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/register',
name: 'register',
builder: (context, state) => const RegisterScreen(),
),
],
// Global redirect for auth
redirect: (context, state) {
final isLoggedIn = ref.read(authStateProvider).valueOrNull != null;
final isAuthRoute = state.matchedLocation.startsWith('/login') ||
state.matchedLocation.startsWith('/register');
if (!isLoggedIn && !isAuthRoute) {
return '/login?redirect=${state.uri}';
}
if (isLoggedIn && isAuthRoute) {
final redirect = state.uri.queryParameters['redirect'];
return redirect ?? '/home';
}
return null;
},
);
```
Deep Linking Configuration:
```dart
// android/app/src/main/AndroidManifest.xml
// Add intent filters for deep linking
// iOS: ios/Runner/Info.plist
// Add URL schemes and associated domains
// Router configuration for deep links
GoRouter(
routes: [
GoRoute(
path: '/product/:id',
builder: (context, state) {
final productId = state.pathParameters['id']!;
final query = state.uri.queryParameters;
return ProductScreen(
id: productId,
variant: query['variant'],
);
},
),
],
);
```
## Performance Optimization
### Widget Optimization
Const Constructors:
```dart
// Good - uses const
class MyWidget extends StatelessWidget {
const MyWidget({super.key}); // const constructor
@override
Widget build(BuildContext context) {
return const Column(
children: [
Text('Static text'), // const widget
Icon(Icons.star), // const widget
],
);
}
}
// Usage
const MyWidget(); // const instantiation
```
RepaintBoundary:
```dart
class ComplexAnimatedWidget extends StatelessWidget {
const ComplexAnimatedWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Static header - doesn't need repaint
const Header(),
// Animated content - isolated repaint
RepaintBoundary(
child: AnimatedContent(),
),
// Static footer - doesn't need repaint
const Footer(),
],
);
}
}
```
ListView Optimization:
```dart
class OptimizedList extends StatelessWidget {
final List<Item> items;
const OptimizedList({required this.items, super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
// Add extent for better performance
itemExtent: 72.0,
// Or use prototypeItem for variable heights
// prototypeItem: const ItemCard(item: prototypeItem),
itemBuilder: (context, index) {
final item = items[index];
return ItemCard(
key: ValueKey(item.id), // Stable key
item: item,
);
},
);
}
}
```
### Memory Management
Image Caching:
```dart
class ImageCacheManager {
static final ImageCacheManager _instance = ImageCacheManager._();
static ImageCacheManager get instance => _instance;
ImageCacheManager._();
void configureCache() {
// Configure image cache size
PaintingBinding.instance.imageCache.maximumSize = 100;
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20; // 50 MB
}
void clearCache() {
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
}
}
```
Provider Disposal:
```dart
@riverpod
class ResourceController extends _$ResourceController {
StreamSubscription? _subscription;
Timer? _timer;
@override
FutureOr<Resource?> build() {
// Setup
_subscription = someStream.listen(_onData);
_timer = Timer.periodic(
const Duration(seconds: 30),
(_) => _refresh(),
);
// Cleanup on disposal
ref.onDispose(() {
_subscription?.cancel();
_timer?.cancel();
});
return null;
}
void _onData(dynamic data) {
// Handle data
}
void _refresh() {
// Refresh logic
}
}
```
## Testing Frameworks
### flutter_test Configuration
```dart
// test/widget_test.dart
void main() {
group('UserScreen', () {
late ProviderContainer container;
setUp(() {
container = ProviderContainer(overrides: [
userRepositoryProvider.overrideWithValue(MockUserRepository()),
]);
});
tearDown(() => container.dispose());
testWidgets('renders user name', (tester) async {
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: const MaterialApp(home: UserScreen(userId: '1')),
),
);
await tester.pumpAndSettle();
expect(find.text('Test User'), findsOneWidget);
});
});
}
```
### Integration Testing
```dart
// integration_test/app_test.dart
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('login and view profile', (tester) async {
await tester.pumpWidget(const MyApp());
// Enter credentials
await tester.enterText(
find.byKey(const Key('email_field')),
'test@example.com',
);
await tester.enterText(
find.byKey(const Key('password_field')),
'password123',
);
// Tap login button
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
// Verify navigation to home
expect(find.byType(HomeScreen), findsOneWidget);
// Navigate to profile
await tester.tap(find.byIcon(Icons.person));
await tester.pumpAndSettle();
// Verify profile screen
expect(find.text('Test User'), findsOneWidget);
});
});
}
```
### Golden Tests
```dart
// test/golden_test.dart
void main() {
testWidgets('UserCard golden test', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Scaffold(
body: UserCard(
user: const User(
id: '1',
name: 'Test User',
email: 'test@example.com',
),
),
),
),
);
await expectLater(
find.byType(UserCard),
matchesGoldenFile('goldens/user_card.png'),
);
});
}
// Run with: flutter test --update-goldens
```
---
Version: 1.0.0
Last Updated: 2025-12-07