Claude-skill-registry Flutter Development Patterns
Project structure, widgets, state management, navigation, theming, platform channels, and best practices for building cross-platform mobile applications with Flutter.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/flutter-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-flutter-development-patterns && rm -rf "$T"
manifest:
skills/data/flutter-patterns/SKILL.mdsource content
Flutter Development Patterns
Current Level: Intermediate
Domain: Mobile Development / Flutter
Overview
Flutter development patterns cover project structure, widgets, state management, navigation, and best practices for building cross-platform mobile applications. Effective Flutter development uses proper architecture, state management, and platform-specific optimizations.
Core Concepts
Table of Contents
- Flutter Setup
- Project Structure
- Widget Patterns
- State Management
- Navigation
- Theming
- Platform Channels
- Async Patterns
- Performance Optimization
- Testing
- Common Packages
- Best Practices
Flutter Setup
Project Initialization
# Using Flutter CLI flutter create my_app --org com.example.myapp --platforms android,ios,web # Using very_good_cli very_good_cli create my_app # Navigate to project cd my_app # Install dependencies flutter pub get # Run the app flutter run
Configuration
# pubspec.yaml name: my_app description: A Flutter application. publish_to: 'none' version: 1.0.0+1 environment: sdk: '>=3.0.0 <4.0.0' flutter: ">=3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 go_router: ^12.0.0 flutter_riverpod: ^2.3.0 dio: ^5.3.0 shared_preferences: ^2.2.0 flutter_secure_storage: ^8.0.0 connectivity_plus: ^5.0.0 cached_network_image: ^3.2.0 flutter_svg: ^2.0.0 image_picker: ^1.0.4 permission_handler: ^11.0.0 intl: ^0.18.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 build_runner: ^2.4.0 json_serializable: ^6.7.0 flutter: uses-material-design: true assets: - assets/images/ - assets/icons/ fonts: - family: Roboto fonts: - asset: fonts/Roboto-Regular.ttf - asset: fonts/Roboto-Bold.ttf weight: 700
Project Structure
Recommended Structure
lib/ ├── main.dart # App entry point ├── app.dart # Root widget ├── core/ # Core functionality │ ├── constants/ # App constants │ ├── error/ # Error handling │ ├── network/ # Network utilities │ ├── router/ # Routing configuration │ └── theme/ # App theming ├── data/ # Data layer │ ├── models/ # Data models │ ├── repositories/ # Data repositories │ └── datasources/ # API and local storage │ ├── api/ # API clients │ └── local/ # Local storage ├── domain/ # Domain layer │ ├── entities/ # Domain entities │ ├── usecases/ # Business logic │ └── repositories/ # Repository interfaces ├── presentation/ # Presentation layer │ ├── pages/ # Screen widgets │ ├── widgets/ # Reusable widgets │ │ ├── common/ # Generic widgets │ │ ├── buttons/ # Button widgets │ │ ├── inputs/ # Input widgets │ │ └── cards/ # Card widgets │ └── providers/ # State providers └── services/ # Services ├── api/ # API service ├── auth/ # Authentication service ├── storage/ # Storage service └── notifications/ # Notification service
Example Files
// lib/core/constants/app_constants.dart class AppConstants { static const String appName = 'MyApp'; static const String defaultCurrency = 'USD'; static const int apiTimeout = 30000; static const int cacheDuration = 86400; // 24 hours } // lib/core/theme/app_theme.dart import 'package:flutter/material.dart'; class AppTheme { static ThemeData get lightTheme => ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF007AFF), brightness: Brightness.light, ), scaffoldBackgroundColor: Colors.white, appBarTheme: const AppBarTheme( centerTitle: true, elevation: 0, ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF007AFF), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ); static ThemeData get darkTheme => ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF007AFF), brightness: Brightness.dark, ), scaffoldBackgroundColor: const Color(0xFF121212), ); } // lib/data/models/user.dart class User { final String id; final String email; final String name; final String? avatar; User({ required this.id, required this.email, required this.name, this.avatar, }); factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], email: json['email'], name: json['name'], avatar: json['avatar'], ); } Map<String, dynamic> toJson() { return { 'id': id, 'email': email, 'name': name, 'avatar': avatar, }; } }
Widget Patterns
Common Widgets
// lib/presentation/widgets/common/loading_widget.dart import 'package:flutter/material.dart'; class LoadingWidget extends StatelessWidget { const LoadingWidget({super.key}); @override Widget build(BuildContext context) { return const Center( child: CircularProgressIndicator(), ); } } // lib/presentation/widgets/common/error_widget.dart class ErrorWidget extends StatelessWidget { final String message; final VoidCallback? onRetry; const ErrorWidget({ super.key, required this.message, this.onRetry, }); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 48, color: Colors.red, ), const SizedBox(height: 16), Text( message, style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center, ), if (onRetry != null) ...[ const SizedBox(height: 16), ElevatedButton( onPressed: onRetry, child: const Text('Retry'), ), ], ], ), ); } } // lib/presentation/widgets/common/empty_widget.dart class EmptyWidget extends StatelessWidget { final String message; final IconData? icon; const EmptyWidget({ super.key, required this.message, this.icon, }); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon ?? Icons.inbox_outlined, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( message, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Colors.grey[600], ), textAlign: TextAlign.center, ), ], ), ); } }
Custom Widgets
// lib/presentation/widgets/buttons/primary_button.dart import 'package:flutter/material.dart'; class PrimaryButton extends StatelessWidget { final String text; final VoidCallback? onPressed; final bool isLoading; final bool isDisabled; const PrimaryButton({ super.key, required this.text, this.onPressed, this.isLoading = false, this.isDisabled = false, }); @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: (isDisabled || isLoading) ? null : onPressed, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text( text, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ); } } // lib/presentation/widgets/inputs/text_input.dart class TextInputWidget extends StatelessWidget { final String label; final String? initialValue; final bool obscureText; final bool enabled; final TextInputType? keyboardType; final Function(String)? onChanged; final String? Function(String)? validator; final Widget? suffixIcon; const TextInputWidget({ super.key, required this.label, this.initialValue, this.obscureText = false, this.enabled = true, this.keyboardType, this.onChanged, this.validator, this.suffixIcon, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), TextFormField( initialValue: initialValue, obscureText: obscureText, enabled: enabled, keyboardType: keyboardType, onChanged: onChanged, validator: validator, decoration: InputDecoration( filled: true, fillColor: Colors.grey[100], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), suffixIcon: suffixIcon, ), ), ], ); } }
State Management
Riverpod Setup
// lib/presentation/providers/auth_provider.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:my_app/data/models/user.dart'; class AuthNotifier extends StateNotifier<AuthState> { AuthNotifier() : super(AuthState.initial()); Future<void> login(String email, String password) async { state = state.copyWith(isLoading: true); try { final user = await _authService.login(email, password); state = state.copyWith( user: user, isAuthenticated: true, isLoading: false, ); } catch (e) { state = state.copyWith( error: e.toString(), isLoading: false, ); } } Future<void> logout() async { await _authService.logout(); state = AuthState.initial(); } void clearError() { state = state.copyWith(error: null); } } class AuthState { final User? user; final bool isAuthenticated; final bool isLoading; final String? error; AuthState({ this.user, required this.isAuthenticated, required this.isLoading, this.error, }); factory AuthState.initial() { return AuthState( isAuthenticated: false, isLoading: false, error: null, ); } AuthState copyWith({ User? user, bool? isAuthenticated, bool? isLoading, String? error, }) { return AuthState( user: user ?? this.user, isAuthenticated: isAuthenticated ?? this.isAuthenticated, isLoading: isLoading ?? this.isLoading, error: error, ); } } final authProvider = StateNotifierProvider<AuthNotifier, AuthState>( (ref) => AuthNotifier(), );
Provider Usage
// lib/presentation/pages/home_page.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class HomePage extends ConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authProvider); return Scaffold( appBar: AppBar( title: const Text('Home'), actions: [ IconButton( icon: const Icon(Icons.logout), onPressed: () async { await ref.read(authProvider.notifier).logout(); }, ), ], ), body: authState.isLoading ? const LoadingWidget() : authState.error != null ? ErrorWidget( message: authState.error!, onRetry: () => ref.read(authProvider.notifier).clearError(), ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Welcome, ${authState.user?.name ?? 'Guest'}'), const SizedBox(height: 16), ElevatedButton( onPressed: () { // Navigate to profile }, child: const Text('View Profile'), ), ], ), ), ); } }
Navigation
Go Router Setup
// lib/core/router/app_router.dart import 'package:go_router/go_router.dart'; import 'package:flutter/material.dart'; enum AppRoute { home, login, register, profile, productDetails, } final goRouter = GoRouter( routes: [ GoRoute( path: '/', name: AppRoute.home, builder: (context, state) => const HomePage(), ), GoRoute( path: '/login', name: AppRoute.login, builder: (context, state) => const LoginPage(), ), GoRoute( path: '/register', name: AppRoute.register, builder: (context, state) => const RegisterPage(), ), GoRoute( path: '/profile/:userId', name: AppRoute.profile, builder: (context, state) { final userId = state.pathParameters['userId']!; return ProfilePage(userId: userId); }, ), GoRoute( path: '/product/:productId', name: AppRoute.productDetails, builder: (context, state) { final productId = state.pathParameters['productId']!; return ProductDetailsPage(productId: productId); }, ), ], errorBuilder: (context, state) => ErrorPage(error: state.error), );
Navigation Helpers
// lib/core/router/navigation_helpers.dart import 'package:go_router/go_router.dart'; extension GoRouterHelper on GoRouter { void navigateToHome() { go(AppRoute.home); } void navigateToLogin() { go(AppRoute.login); } void navigateToProfile(String userId) { push(AppRoute.profile, pathParameters: {'userId': userId}); } void navigateToProductDetails(String productId) { push(AppRoute.productDetails, pathParameters: {'productId': productId}); } void goBack() { pop(); } } // Usage class ProductCard extends StatelessWidget { final String productId; final String productName; const ProductCard({ super.key, required this.productId, required this.productName, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { context.goRouter.navigateProductDetails(productId); }, child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Text(productName), ), ), ); } }
Theming
Theme Provider
// lib/presentation/providers/theme_provider.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; enum ThemeMode { light, dark, system, } class ThemeNotifier extends StateNotifier<ThemeMode> { ThemeNotifier() : super(ThemeMode.system); void setTheme(ThemeMode mode) { state = mode; } void toggleTheme() { switch (state) { case ThemeMode.light: state = ThemeMode.dark; break; case ThemeMode.dark: state = ThemeMode.light; break; case ThemeMode.system: final isDark = WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark; state = isDark ? ThemeMode.light : ThemeMode.dark; break; } } } final themeProvider = StateNotifierProvider<ThemeNotifier, ThemeMode>( (ref) => ThemeNotifier(), ); final themeModeProvider = Provider<ThemeData>((ref) { final mode = ref.watch(themeProvider); final brightness = MediaQuery.of(ref.context).platformBrightness; switch (mode) { case ThemeMode.light: return AppTheme.lightTheme; case ThemeMode.dark: return AppTheme.darkTheme; case ThemeMode.system: return brightness == Brightness.dark ? AppTheme.darkTheme : AppTheme.lightTheme; } });
Platform Channels
Native Communication
// lib/services/platform/platform_service.dart import 'package:flutter/services.dart'; class PlatformService { static const MethodChannel _channel = MethodChannel('com.example.myapp/platform'); static Future<String> getDeviceName() async { try { final result = await _channel.invokeMethod('getDeviceName'); return result as String; } on PlatformException catch (e) { throw Exception('Failed to get device name: ${e.message}'); } } static Future<void> openSettings() async { try { await _channel.invokeMethod('openSettings'); } on PlatformException catch (e) { throw Exception('Failed to open settings: ${e.message}'); } } } // Android: android/app/src/main/kotlin/com/example/myapp/MainActivity.kt package com.example.myapp import android.os.Bundle import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "com.example.myapp/platform" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "getDeviceName" -> { result.invokeMethod("getDeviceName", null, object : Bundle()) } "openSettings" -> { val intent = android.content.Intent( android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS ) intent.addCategory(android.content.Intent.CATEGORY_DEFAULT) intent.data = context.packageName startActivity(intent) result.success(null) } else -> result.notImplemented() } } } } // iOS: ios/Runner/AppDelegate.swift import Flutter import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as? FlutterViewController let channel = FlutterMethodChannel( name: "com.example.myapp/platform", binaryMessenger: controller!.binaryMessenger ) channel.setMethodCallHandler { [weak self] (call, result) in switch call.method { case "getDeviceName": let name = UIDevice.current.name result(name) case "openSettings": if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) result(Bool(true)) } else { result(FlutterError(code: "UNAVAILABLE", message: "Settings not available", details: nil)) } default: result(FlutterMethodNotImplemented) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
Async Patterns
Async/Await
// lib/services/api/api_service.dart import 'package:dio/dio.dart'; import 'package:my_app/core/error/exceptions.dart'; class ApiService { final Dio _dio; ApiService(this._dio); Future<T> get<T>( String path, { Map<String, dynamic>? queryParameters, Options? options, }) async { try { final response = await _dio.get( path, queryParameters: queryParameters, options: options, ); return response.data as T; } on DioException catch (e) { throw ApiException.fromDioException(e); } } Future<T> post<T>( String path, { dynamic data, Options? options, }) async { try { final response = await _dio.post( path, data: data, options: options, ); return response.data as T; } on DioException catch (e) { throw ApiException.fromDioException(e); } } } // lib/core/error/exceptions.dart class ApiException implements Exception { final String message; final int? statusCode; ApiException(this.message, {this.statusCode}); factory ApiException.fromDioException(DioException e) { switch (e.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: return ApiException('Connection timeout'); case DioExceptionType.badResponse: return ApiException( e.response?.statusMessage ?? 'Server error', statusCode: e.response?.statusCode, ); case DioExceptionType.cancel: return ApiException('Request cancelled'); default: return ApiException('Network error'); } } }
Performance Optimization
List Optimization
// lib/presentation/widgets/common/optimized_list.dart import 'package:flutter/material.dart'; class OptimizedList extends StatelessWidget { final List<dynamic> items; final Widget Function(dynamic item) itemBuilder; final String Function(dynamic item) keyExtractor; const OptimizedList({ super.key, required this.items, required this.itemBuilder, required this.keyExtractor, }); @override Widget build(BuildContext context) { return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; return itemBuilder(item); }, cacheExtent: 200, addAutomaticKeepAlives: true, ); } } // Usage class ProductListPage extends StatelessWidget { const ProductListPage({super.key}); @override Widget build(BuildContext context) { final products = Provider.of<ProductProvider>(context).products; return OptimizedList( items: products, keyExtractor: (product) => product.id, itemBuilder: (product) => ProductCard(product: product), ); } }
Testing
Widget Testing
// test/presentation/widgets/buttons/primary_button_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:my_app/presentation/widgets/buttons/primary_button.dart'; void main() { group('PrimaryButton', () { testWidgets('renders correctly', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: PrimaryButton( text: 'Click me', onPressed: () {}, ), ), ), ); expect(find.text('Click me'), findsOneWidget); }); testWidgets('shows loading indicator when isLoading is true', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: PrimaryButton( text: 'Click me', isLoading: true, onPressed: () {}, ), ), ), ); expect(find.byType(CircularProgressIndicatorIndicator), findsOneWidget); expect(find.text('Click me'), findsNothing); }); testWidgets('is disabled when isDisabled is true', (tester) async { bool onPressedCalled = false; await tester.pumpWidget( MaterialApp( home: Scaffold( body: PrimaryButton( text: 'Click me', isDisabled: true, onPressed: () { onPressedCalled = true; }, ), ), ), ); await tester.tap(find.byType(ElevatedButton)); expect(onPressedCalled, false); }); }); }
Common Packages
Essential Packages
# pubspec.yaml dependencies: # Navigation go_router: ^12.0.0 # State Management flutter_riverpod: ^2.3.0 # Networking dio: ^5.3.0 connectivity_plus: ^5.0.0 # Storage shared_preferences: ^2.2.0 flutter_secure_storage: ^8.0.0 hive: ^2.2.3 sqflite: ^2.3.0 # UI Components cached_network_image: ^3.2.0 flutter_svg: ^2.0.0 image_picker: ^1.0.4 permission_handler: ^11.0.0 flutter_local_notifications: ^16.0.0 url_launcher: ^6.1.11 share_plus: ^7.2.1 # Utilities intl: ^0.18.0 path_provider: ^2.1.1 device_info_plus: ^9.1.0 # Firebase firebase_core: ^2.15.0 firebase_auth: ^4.7.3 cloud_firestore: ^4.8.5 firebase_messaging: ^14.6.4 firebase_analytics: ^10.4.5 # Maps & Location google_maps_flutter: ^2.5.0 geolocator: ^10.0.0 permission_handler: ^11.0.0 # Testing flutter_test: ^0.7.0 mockito: ^5.4.1 build_runner: ^2.4.0 json_serializable: ^6.7.0
Best Practices
Performance Best Practices
// 1. Use const widgets where possible class GoodExample extends StatelessWidget { const GoodExample({super.key}); @override Widget build(BuildContext context) { return const Text('Hello'); // Good } } class BadExample extends StatelessWidget { const BadExample({super.key}); @override Widget build(BuildContext context) { return Text('Hello'); // Bad: Creates new widget every build } } // 2. Use ListView.builder for long lists class GoodListExample extends StatelessWidget { final List<String> items; const GoodListExample({super.key, required this.items}); @override Widget build(BuildContext context) { return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: Text(items[index])); }, ); } } class BadListExample extends StatelessWidget { final List<String> items; const BadListExample({super.key, required this.items}); @override Widget build(BuildContext context) { return ListView( children: items.map((item) => ListTile(title: Text(item))).toList(), // Bad: All widgets built at once ); } } // 3. Use memo for expensive widgets class ExpensiveWidget extends StatelessWidget { final String data; const ExpensiveWidget({super.key, required this.data}); @override Widget build(BuildContext context) { return _ExpensiveWidgetContent(data: data); } } class _ExpensiveWidgetContent extends StatelessWidget { final String data; const _ExpensiveWidgetContent({super.key, required this.data}); @override Widget build(BuildContext context) { // Expensive computation final processedData = data.toUpperCase(); return Text(processedData); } } // 4. Avoid rebuilds with const constructors class RebuildExample extends StatelessWidget { const RebuildExample({super.key}); @override Widget build(BuildContext context) { return const Column( children: [ Text('Static text 1'), Text('Static text 2'), ], ); } } // 5. Use Provider/Riverpod for state management class ProviderExample extends StatelessWidget { const ProviderExample({super.key}); @override Widget build(BuildContext context) { final counter = Provider.of<CounterProvider>(context); return Text('Count: ${counter.count}'); } }
Quick Start
Flutter Project Structure
lib/ main.dart models/ user.dart services/ api_service.dart screens/ home_screen.dart widgets/ custom_button.dart utils/ constants.dart
State Management (Provider)
class CounterProvider extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } // Usage Consumer<CounterProvider>( builder: (context, counter, child) { return Text('Count: ${counter.count}'); }, )
Production Checklist
- Project Structure: Organized project structure
- State Management: Choose state management solution
- Navigation: Navigation setup
- Theming: Theme configuration
- Platform Channels: Native integration if needed
- Async Patterns: Proper async/await usage
- Performance: Performance optimization
- Testing: Unit and widget tests
- Packages: Use appropriate packages
- Documentation: Document code
- CI/CD: Automated builds
- Error Handling: Comprehensive error handling
Anti-patterns
❌ Don't: No State Management
// ❌ Bad - No state management int count = 0; // State scattered everywhere!
// ✅ Good - State management class CounterProvider extends ChangeNotifier { int count = 0; // Centralized state }
❌ Don't: Build in Build
// ❌ Bad - Build in build Widget build(BuildContext context) { return FutureBuilder( future: fetchData(), // Called on every build! builder: (context, snapshot) => ... ) }
// ✅ Good - Initialize once class _MyWidgetState extends State<MyWidget> { late Future<Data> _data; @override void initState() { super.initState(); _data = fetchData(); // Called once } Widget build(BuildContext context) { return FutureBuilder( future: _data, builder: (context, snapshot) => ... ) } }
Integration Points
- React Native Patterns (
) - Cross-platform patterns31-mobile-development/react-native-patterns/ - Mobile CI/CD (
) - CI/CD for Flutter31-mobile-development/mobile-ci-cd/ - App Distribution (
) - Distribution31-mobile-development/app-distribution/