UNPKG

opencode-agent-kit

Version:

Multi-stack OpenCode agent toolkit — 33+ specialized AI agents, 200+ skills, 46 commands, 8 MCP servers (Nuxt, React, Node.js, Laravel, CI3, Android, Flutter, DevOps, SEO, SonarQube, and more)

293 lines (250 loc) 7.96 kB
--- name: flutter description: Comprehensive Flutter patterns, architecture, state management, testing, and platform integration for production-grade cross-platform apps. --- # Flutter Patterns & Best Practices Comprehensive reference for building production-grade Flutter applications. ## Architecture Patterns ### Clean Architecture Layer Structure ``` lib/ ├── core/ # Shared utilities, constants, theme ├── data/ │ ├── datasources/ # Remote (API) + Local (DB) sources │ ├── models/ # JSON serializable models (fromJson/toJson) │ └── repositories/ # Repository implementations ├── domain/ │ ├── entities/ # Pure Dart domain objects │ ├── repositories/ # Abstract repository interfaces │ └── usecases/ # Business logic use cases ├── presentation/ │ ├── providers/ # State notifiers (Bloc/Riverpod) │ ├── screens/ # Screen-level widgets │ └── widgets/ # Reusable widgets ├── di/ # Dependency injection setup ├── main.dart └── app.dart ``` ### Repository Pattern (Offline-First) ```dart abstract class ProductRepository { Future<Either<Failure, List<Product>>> getProducts(); Future<Either<Failure, Product>> getProduct(String id); } class ProductRepositoryImpl implements ProductRepository { final ProductRemoteDataSource remote; final ProductLocalDataSource local; ProductRepositoryImpl({required this.remote, required this.local}); @override Future<Either<Failure, List<Product>>> getProducts() async { try { final remoteProducts = await remote.getProducts(); await local.cacheProducts(remoteProducts); return Right(remoteProducts); } on ServerException { try { final localProducts = await local.getCachedProducts(); return Right(localProducts); } on CacheException { return Left(CacheFailure()); } } } } ``` ## State Management ### Riverpod ```dart // Provider definition final productRepositoryProvider = Provider<ProductRepository>((ref) { return ProductRepositoryImpl( remote: ref.watch(productRemoteDataSourceProvider), local: ref.watch(productLocalDataSourceProvider), ); }); // AsyncNotifierProvider final productsProvider = AsyncNotifierProvider<ProductsNotifier, List<Product>>( ProductsNotifier.new, ); class ProductsNotifier extends AsyncNotifier<List<Product>> { @override Future<List<Product>> build() async { final repo = ref.read(productRepositoryProvider); return repo.getProducts(); } Future<void> addProduct(Product product) async { } } // In widget class ProductList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productsProvider); return productsAsync.when( data: (products) => ListView.builder(/*...*/), loading: () => const CircularProgressIndicator(), error: (e, _) => Text('Error: $e'), ); } } ``` ### Bloc ```dart // Event sealed class ProductEvent {} final class LoadProducts extends ProductEvent {} final class SearchProducts extends ProductEvent { final String query; SearchProducts(this.query); } // State sealed class ProductState {} final class ProductInitial extends ProductState {} final class ProductLoading extends ProductState {} final class ProductLoaded extends ProductState { final List<Product> products; ProductLoaded(this.products); } final class ProductError extends ProductState { final String message; ProductError(this.message); } // Bloc class ProductBloc extends Bloc<ProductEvent, ProductState> { final ProductRepository _repository; ProductBloc(this._repository) : super(ProductInitial()) { on<LoadProducts>(_onLoadProducts); } Future<void> _onLoadProducts(LoadProducts event, Emitter<ProductState> emit) async { emit(ProductLoading()); final result = await _repository.getProducts(); result.fold( (failure) => emit(ProductError(failure.message)), (products) => emit(ProductLoaded(products)), ); } } ``` ## Navigation (GoRouter) ```dart final router = GoRouter( initialLocation: '/products', routes: [ ShellRoute( builder: (context, state, child) => MainShell(child: child), routes: [ GoRoute( path: '/products', builder: (context, state) => const ProductListScreen(), routes: [ GoRoute( path: ':id', builder: (context, state) => ProductDetailScreen( id: state.pathParameters['id']!, ), ), ], ), GoRoute( path: '/cart', builder: (context, state) => const CartScreen(), ), ], ), ], ); ``` ## Testing Strategy | Layer | Test Type | Tools | |-------|-----------|-------| | Domain (entities, usecases) | Unit test | `flutter_test`, `mocktail` | | Data (repositories, datasources) | Unit + Integration | `mocktail`, `http` mocking | | Presentation (providers, blocs) | Unit (bloc test) | `bloc_test`, `riverpod` test utils | | Widgets | Widget test | `WidgetTester`, `Finder` | | Full app | Integration test | `integration_test` package | | Visual | Golden test | `golden_toolkit`, `alchemist` | ### Widget Test Example ```dart testWidgets('ProductList shows loading state', (tester) async { await tester.pumpWidget( MaterialApp( home: BlocProvider( create: (_) => ProductBloc(mockRepo)..add(LoadProducts()), child: ProductListScreen(), ), ), ); expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); expect(find.text('Product 1'), findsOneWidget); }); ``` ## Platform Integration ### Method Channel ```dart // Dart side class PlatformService { static const _channel = MethodChannel('com.example.app/platform'); Future<String> getDeviceId() async { try { return await _channel.invokeMethod('getDeviceId'); } on PlatformException catch (e) { throw PlatformException(code: e.code, message: e.message); } } } ``` ### Firebase Integration ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } ``` ## Material Design 3 Theming ```dart class AppTheme { static ThemeData light() { final colorScheme = ColorScheme.fromSeed( seedColor: const Color(0xFF6750A4), brightness: Brightness.light, ); return ThemeData( useMaterial3: true, colorScheme: colorScheme, fontFamily: 'Inter', appBarTheme: AppBarTheme( centerTitle: true, backgroundColor: colorScheme.surface, ), cardTheme: CardTheme( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ); } static ThemeData dark() { final colorScheme = ColorScheme.fromSeed( seedColor: const Color(0xFF6750A4), brightness: Brightness.dark, ); return ThemeData( useMaterial3: true, colorScheme: colorScheme, fontFamily: 'Inter', ); } } ``` ## Performance Best Practices 1. **const constructors** — Always use `const` for widgets that don't change 2. **RepaintBoundary** — Wrap complex animations with `RepaintBoundary` 3. **ListView.builder** — Use builder instead of `ListView(children: [...])` 4. **Image caching** — Use `cached_network_image` or `ImageCache` 5. **Avoid rebuilds** — Use `const`, `Selector` (Bloc), `select` (Riverpod) 6. **Lazy loading** — Use `flutter_bloc` `Emitter` or Riverpod `AsyncNotifier` for pagination 7. **Memory** — Dispose controllers, streams, and subscriptions in `dispose()`