Back to Subjects
Flutter Cheatsheet
Comprehensive Flutter development guide with widgets, state management, animations, and mobile app development.
Flutter Development Cheatsheet
Getting Started
Installation & Setup
# Install Flutter SDK
# Download from https://flutter.dev/docs/get-started/install
# Check Flutter installation
flutter doctor
# Create new Flutter project
flutter create my_app
cd my_app
# Run the app
flutter run
# Build for different platforms
flutter build apk # Android APK
flutter build ios # iOS (requires Xcode)
flutter build web # Web
flutter build windows # Windows desktop
Project Structure
lib/
├── main.dart # Entry point
├── models/ # Data models
├── screens/ # UI screens
├── widgets/ # Custom widgets
├── services/ # API services
├── providers/ # State management
├── utils/ # Utility functions
└── constants/ # App constants
pubspec.yaml # Dependencies
android/ # Android-specific files
ios/ # iOS-specific files
web/ # Web-specific files
Basic Widgets
Hello World App
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Hello Flutter'),
),
body: Center(
child: Text(
'Hello, World!',
style: TextStyle(fontSize: 24),
),
),
);
}
}
Common Widgets
// Text Widget
Text(
'Hello Flutter',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
)
// Container Widget
Container(
width: 200,
height: 100,
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey),
),
child: Text('Container Content'),
)
// Image Widget
Image.asset('assets/images/logo.png')
Image.network('https://example.com/image.jpg')
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
)
// Icon Widget
Icon(
Icons.home,
size: 30,
color: Colors.blue,
)
// Button Widgets
ElevatedButton(
onPressed: () {
print('Button pressed');
},
child: Text('Elevated Button'),
)
TextButton(
onPressed: () {},
child: Text('Text Button'),
)
OutlinedButton(
onPressed: () {},
child: Text('Outlined Button'),
)
// FloatingActionButton
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
)
Layout Widgets
Column and Row
// Column - Vertical layout
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
],
)
// Row - Horizontal layout
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.star),
Text('Rating'),
Text('4.5'),
],
)
// Expanded - Takes available space
Row(
children: [
Expanded(
flex: 2,
child: Container(color: Colors.red, height: 50),
),
Expanded(
flex: 1,
child: Container(color: Colors.blue, height: 50),
),
],
)
Stack and Positioned
// Stack - Overlay widgets
Stack(
children: [
Container(
width: 200,
height: 200,
color: Colors.blue,
),
Positioned(
top: 10,
right: 10,
child: Icon(Icons.favorite, color: Colors.red),
),
Positioned(
bottom: 10,
left: 10,
child: Text('Bottom Left'),
),
],
)
// Center Widget
Center(
child: Text('Centered Text'),
)
// Align Widget
Align(
alignment: Alignment.topRight,
child: Icon(Icons.close),
)
ListView and GridView
// ListView - Scrollable list
ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('John Doe'),
subtitle: Text('Software Developer'),
trailing: Icon(Icons.arrow_forward),
onTap: () {},
),
ListTile(
leading: Icon(Icons.person),
title: Text('Jane Smith'),
subtitle: Text('Designer'),
),
],
)
// ListView.builder - Dynamic list
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)
// GridView - Grid layout
GridView.count(
crossAxisCount: 2,
children: List.generate(6, (index) {
return Container(
margin: EdgeInsets.all(8),
color: Colors.blue,
child: Center(
child: Text('Item $index'),
),
);
}),
)
// GridView.builder
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.0,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
child: Center(
child: Text(items[index]),
),
);
},
)
State Management
StatefulWidget
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _decrementCounter() {
setState(() {
_counter--;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Counter Value:',
style: TextStyle(fontSize: 18),
),
Text(
'$_counter',
style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _decrementCounter,
child: Icon(Icons.remove),
),
ElevatedButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
],
),
],
),
),
);
}
}
Provider Pattern
// pubspec.yaml
dependencies:
provider: ^6.0.0
// counter_model.dart
import 'package:flutter/foundation.dart';
class CounterModel with ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
void decrement() {
_counter--;
notifyListeners();
}
void reset() {
_counter = 0;
notifyListeners();
}
}
// main.dart
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
// Using Provider in Widget
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counter, child) {
return Column(
children: [
Text('Count: ${counter.counter}'),
ElevatedButton(
onPressed: counter.increment,
child: Text('Increment'),
),
],
);
},
);
}
}
// Alternative usage
class AnotherWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<CounterModel>(context);
return Text('Count: ${counter.counter}');
}
}
Bloc Pattern
// pubspec.yaml
dependencies:
flutter_bloc: ^8.0.0
// counter_event.dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// counter_state.dart
class CounterState {
final int counter;
CounterState(this.counter);
}
// counter_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<Increment>((event, emit) {
emit(CounterState(state.counter + 1));
});
on<Decrement>((event, emit) {
emit(CounterState(state.counter - 1));
});
}
}
// Using Bloc
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: CounterView(),
);
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Center(
child: Text('${state.counter}'),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Decrement()),
child: Icon(Icons.remove),
),
],
),
);
}
}
Navigation
Basic Navigation
// Navigate to new screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
// Navigate back
Navigator.pop(context);
// Navigate and replace current screen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => NewScreen()),
);
// Navigate and clear all previous screens
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(Route<dynamic> route) => false,
);
Named Routes
// main.dart
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
},
)
// Navigate using named routes
Navigator.pushNamed(context, '/second');
Navigator.pushReplacementNamed(context, '/third');
// Passing arguments
Navigator.pushNamed(
context,
'/second',
arguments: {'id': 123, 'name': 'John'},
);
// Receiving arguments
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
return Scaffold(
body: Text('ID: ${args['id']}, Name: ${args['name']}'),
);
}
}
Advanced Navigation (Go Router)
// pubspec.yaml
dependencies:
go_router: ^6.0.0
// main.dart
import 'package:go_router/go_router.dart';
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
path: '/details/:id',
builder: (context, state) {
final id = state.params['id']!;
return DetailsScreen(id: id);
},
),
GoRoute(
path: '/profile',
builder: (context, state) => ProfileScreen(),
routes: [
GoRoute(
path: '/settings',
builder: (context, state) => SettingsScreen(),
),
],
),
],
);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
// Usage
context.go('/details/123');
context.push('/profile/settings');
Forms and Input
TextFormField and Form
class UserForm extends StatefulWidget {
@override
_UserFormState createState() => _UserFormState();
}
class _UserFormState extends State<UserForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
// Form is valid, process data
print('Name: ${_nameController.text}');
print('Email: ${_emailController.text}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User Form')),
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submitForm,
child: Text('Submit'),
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 50),
),
),
],
),
),
),
);
}
}
Other Input Widgets
// Checkbox
bool isChecked = false;
Checkbox(
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = value!;
});
},
)
// Radio Button
int selectedValue = 1;
Column(
children: [
RadioListTile<int>(
title: Text('Option 1'),
value: 1,
groupValue: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value!;
});
},
),
RadioListTile<int>(
title: Text('Option 2'),
value: 2,
groupValue: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value!;
});
},
),
],
)
// Switch
bool isSwitched = false;
Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
});
},
)
// Slider
double sliderValue = 50.0;
Slider(
value: sliderValue,
min: 0.0,
max: 100.0,
divisions: 10,
label: sliderValue.round().toString(),
onChanged: (value) {
setState(() {
sliderValue = value;
});
},
)
// DropdownButton
String dropdownValue = 'Option 1';
DropdownButton<String>(
value: dropdownValue,
items: ['Option 1', 'Option 2', 'Option 3']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
)
HTTP Requests
Using http package
// pubspec.yaml
dependencies:
http: ^0.13.5
// api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
static const String baseUrl = 'https://jsonplaceholder.typicode.com';
// GET request
static Future<List<dynamic>> fetchPosts() async {
try {
final response = await http.get(
Uri.parse('$baseUrl/posts'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load posts');
}
} catch (e) {
throw Exception('Network error: $e');
}
}
// POST request
static Future<Map<String, dynamic>> createPost({
required String title,
required String body,
required int userId,
}) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/posts'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'title': title,
'body': body,
'userId': userId,
}),
);
if (response.statusCode == 201) {
return json.decode(response.body);
} else {
throw Exception('Failed to create post');
}
} catch (e) {
throw Exception('Network error: $e');
}
}
// PUT request
static Future<Map<String, dynamic>> updatePost({
required int id,
required String title,
required String body,
required int userId,
}) async {
final response = await http.put(
Uri.parse('$baseUrl/posts/$id'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'id': id,
'title': title,
'body': body,
'userId': userId,
}),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to update post');
}
}
// DELETE request
static Future<void> deletePost(int id) async {
final response = await http.delete(
Uri.parse('$baseUrl/posts/$id'),
);
if (response.statusCode != 200) {
throw Exception('Failed to delete post');
}
}
}
Using API in Widget
class PostsScreen extends StatefulWidget {
@override
_PostsScreenState createState() => _PostsScreenState();
}
class _PostsScreenState extends State<PostsScreen> {
List<dynamic> posts = [];
bool isLoading = true;
String? error;
@override
void initState() {
super.initState();
_loadPosts();
}
Future<void> _loadPosts() async {
try {
setState(() {
isLoading = true;
error = null;
});
final fetchedPosts = await ApiService.fetchPosts();
setState(() {
posts = fetchedPosts;
isLoading = false;
});
} catch (e) {
setState(() {
error = e.toString();
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Posts')),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: _loadPosts,
child: Icon(Icons.refresh),
),
);
}
Widget _buildBody() {
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
if (error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $error'),
SizedBox(height: 16),
ElevatedButton(
onPressed: _loadPosts,
child: Text('Retry'),
),
],
),
);
}
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
title: Text(
post['title'],
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(post['body']),
leading: CircleAvatar(
child: Text(post['id'].toString()),
),
),
);
},
);
}
}
Local Storage
SharedPreferences
// pubspec.yaml
dependencies:
shared_preferences: ^2.0.15
// preferences_service.dart
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesService {
static SharedPreferences? _prefs;
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// String methods
static Future<void> setString(String key, String value) async {
await _prefs?.setString(key, value);
}
static String? getString(String key) {
return _prefs?.getString(key);
}
// Int methods
static Future<void> setInt(String key, int value) async {
await _prefs?.setInt(key, value);
}
static int? getInt(String key) {
return _prefs?.getInt(key);
}
// Bool methods
static Future<void> setBool(String key, bool value) async {
await _prefs?.setBool(key, value);
}
static bool? getBool(String key) {
return _prefs?.getBool(key);
}
// List<String> methods
static Future<void> setStringList(String key, List<String> value) async {
await _prefs?.setStringList(key, value);
}
static List<String>? getStringList(String key) {
return _prefs?.getStringList(key);
}
// Remove key
static Future<void> remove(String key) async {
await _prefs?.remove(key);
}
// Clear all
static Future<void> clear() async {
await _prefs?.clear();
}
}
// Usage
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PreferencesService.init();
runApp(MyApp());
}
// In your widget
class SettingsScreen extends StatefulWidget {
@override
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool darkMode = false;
String username = '';
@override
void initState() {
super.initState();
_loadSettings();
}
void _loadSettings() {
setState(() {
darkMode = PreferencesService.getBool('dark_mode') ?? false;
username = PreferencesService.getString('username') ?? '';
});
}
void _saveDarkMode(bool value) {
setState(() {
darkMode = value;
});
PreferencesService.setBool('dark_mode', value);
}
void _saveUsername(String value) {
setState(() {
username = value;
});
PreferencesService.setString('username', value);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: Column(
children: [
SwitchListTile(
title: Text('Dark Mode'),
value: darkMode,
onChanged: _saveDarkMode,
),
Padding(
padding: EdgeInsets.all(16),
child: TextFormField(
initialValue: username,
decoration: InputDecoration(labelText: 'Username'),
onChanged: _saveUsername,
),
),
],
),
);
}
}
Animations
Basic Animations
class AnimatedContainerExample extends StatefulWidget {
@override
_AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool _isExpanded = false;
Color _color = Colors.blue;
double _width = 100;
double _height = 100;
void _toggleAnimation() {
setState(() {
_isExpanded = !_isExpanded;
_color = _isExpanded ? Colors.red : Colors.blue;
_width = _isExpanded ? 200 : 100;
_height = _isExpanded ? 200 : 100;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedContainer(
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
width: _width,
height: _height,
color: _color,
child: Center(
child: Text(
'Tap me!',
style: TextStyle(color: Colors.white),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggleAnimation,
child: Icon(Icons.play_arrow),
),
);
}
}
// Fade Transition
class FadeTransitionExample extends StatefulWidget {
@override
_FadeTransitionExampleState createState() => _FadeTransitionExampleState();
}
class _FadeTransitionExampleState extends State<FadeTransitionExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FadeTransition(
opacity: _animation,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
'Fade Me!',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => _controller.forward(),
child: Icon(Icons.play_arrow),
heroTag: "play",
),
SizedBox(width: 10),
FloatingActionButton(
onPressed: () => _controller.reverse(),
child: Icon(Icons.stop),
heroTag: "stop",
),
],
),
);
}
}
Hero Animation
// First Screen
class HeroFirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero Animation')),
body: Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HeroSecondScreen()),
);
},
child: Hero(
tag: 'hero-image',
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.star, color: Colors.white, size: 50),
),
),
),
),
);
}
}
// Second Screen
class HeroSecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero Detail')),
body: Center(
child: Hero(
tag: 'hero-image',
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
),
child: Icon(Icons.star, color: Colors.white, size: 150),
),
),
),
);
}
}
Testing
Unit Tests
// test/counter_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/models/counter_model.dart';
void main() {
group('CounterModel', () {
test('should start with count 0', () {
final counter = CounterModel();
expect(counter.counter, 0);
});
test('should increment count', () {
final counter = CounterModel();
counter.increment();
expect(counter.counter, 1);
});
test('should decrement count', () {
final counter = CounterModel();
counter.increment();
counter.decrement();
expect(counter.counter, 0);
});
test('should reset count to 0', () {
final counter = CounterModel();
counter.increment();
counter.increment();
counter.reset();
expect(counter.counter, 0);
});
});
}
Widget Tests
// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/main.dart';
void main() {
group('MyApp Widget Tests', () {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
testWidgets('Should display app title', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('Flutter Demo Home Page'), findsOneWidget);
});
testWidgets('Should have increment and decrement buttons', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.byIcon(Icons.add), findsOneWidget);
expect(find.byIcon(Icons.remove), findsOneWidget);
});
});
}
Integration Tests
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:myapp/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('tap on the floating action button, verify counter', (tester) async {
app.main();
await tester.pumpAndSettle();
// Verify the counter starts at 0
expect(find.text('0'), findsOneWidget);
// Finds the floating action button to tap on
final Finder fab = find.byTooltip('Increment');
// Emulate a tap on the floating action button
await tester.tap(fab);
// Trigger a frame
await tester.pumpAndSettle();
// Verify the counter increments by 1
expect(find.text('1'), findsOneWidget);
});
});
}
Platform-Specific Code
Method Channels
// lib/platform_service.dart
import 'package:flutter/services.dart';
class PlatformService {
static const MethodChannel _channel = MethodChannel('com.example.myapp/platform');
static Future<String> getPlatformVersion() async {
try {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
} on PlatformException catch (e) {
return "Failed to get platform version: '${e.message}'.";
}
}
static Future<void> showNativeAlert(String message) async {
try {
await _channel.invokeMethod('showAlert', {'message': message});
} on PlatformException catch (e) {
print("Failed to show alert: '${e.message}'.");
}
}
}
// Usage in widget
class PlatformExample extends StatefulWidget {
@override
_PlatformExampleState createState() => _PlatformExampleState();
}
class _PlatformExampleState extends State<PlatformExample> {
String _platformVersion = 'Unknown';
@override
void initState() {
super.initState();
_getPlatformVersion();
}
Future<void> _getPlatformVersion() async {
String platformVersion = await PlatformService.getPlatformVersion();
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Platform Service')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Running on: $_platformVersion'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
PlatformService.showNativeAlert('Hello from Flutter!');
},
child: Text('Show Native Alert'),
),
],
),
),
);
}
}
Android Implementation
// android/app/src/main/kotlin/com/example/myapp/MainActivity.kt
package com.example.myapp
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.os.Build
import androidx.appcompat.app.AlertDialog
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) {
"getPlatformVersion" -> {
val version = "Android ${Build.VERSION.RELEASE}"
result.success(version)
}
"showAlert" -> {
val message = call.argument<String>("message")
showAlert(message ?: "Default message")
result.success(null)
}
else -> {
result.notImplemented()
}
}
}
}
private fun showAlert(message: String) {
AlertDialog.Builder(this)
.setTitle("Native Alert")
.setMessage(message)
.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
.show()
}
}
iOS Implementation
// ios/Runner/AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let platformChannel = FlutterMethodChannel(name: "com.example.myapp/platform",
binaryMessenger: controller.binaryMessenger)
platformChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
case "showAlert":
if let args = call.arguments as? Dictionary<String, Any>,
let message = args["message"] as? String {
self.showAlert(message: message)
}
result(nil)
default:
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func showAlert(message: String) {
let alert = UIAlertController(title: "Native Alert", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
if let controller = window?.rootViewController {
controller.present(alert, animated: true)
}
}
}
Best Practices
Project Structure
lib/
├── main.dart
├── app.dart
├── core/
│ ├── constants/
│ ├── errors/
│ ├── themes/
│ └── utils/
├── features/
│ ├── authentication/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ └── home/
│ ├── data/
│ ├── domain/
│ └── presentation/
├── shared/
│ ├── widgets/
│ ├── services/
│ └── models/
└── config/
├── routes/
└── themes/
Performance Tips
- Use const constructors when possible
- Implement proper ListView builders for large lists
- Avoid rebuilding expensive widgets with proper state management
- Use AutomaticKeepAliveClientMixin for tabs
- Optimize images with proper formats and sizes
- Use RepaintBoundary for complex widgets
- Profile your app with Flutter Inspector
- Minimize widget rebuilds with keys and proper structure
- Use lazy loading for data and images
- Implement proper error handling and loading states
Code Quality
- Follow Dart style guide and use
dart format - Use meaningful names for variables and functions
- Write comprehensive tests (unit, widget, integration)
- Document public APIs with dartdoc comments
- Use static analysis with
dart analyze - Handle null safety properly
- Implement proper error handling
- Use dependency injection for testability
- Keep widgets small and focused
- Use version control effectively