Flutter Best Practices
BrewKits' recommended best practices for Flutter package development.
Code Organization
1. Directory Structure
lib/
├── src/
│ ├── models/
│ ├── services/
│ ├── widgets/
│ └── utils/
└── my_package.dart # Public API
2. Public API Design
Only export what users need:
// ✅ Good
library my_package;
export 'src/widgets/awesome_widget.dart';
export 'src/models/awesome_model.dart';
// ❌ Bad - don't export internal utilities
// export 'src/utils/internal_helper.dart';
3. Use Barrel Files Wisely
// lib/src/widgets/widgets.dart
export 'awesome_widget.dart';
export 'cool_widget.dart';
// lib/my_package.dart
export 'src/widgets/widgets.dart';
Code Quality
1. Enable Strict Linting
Add flutter_lints to your pubspec.yaml:
dev_dependencies:
flutter_lints: ^4.0.0
Create analysis_options.yaml:
include: package:flutter_lints/flutter.yaml
linter:
rules:
- always_declare_return_types
- avoid_print
- prefer_const_constructors
- prefer_const_declarations
2. Null Safety
Always use null safety:
// ✅ Good
String? getName() {
return name;
}
// Use null-aware operators
final displayName = getName() ?? 'Unknown';
// ❌ Bad - avoid using dynamic
dynamic getData() { }
3. Immutability
Prefer immutable classes:
// ✅ Good
class User {
const User({
required this.id,
required this.name,
});
final String id;
final String name;
User copyWith({
String? id,
String? name,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
);
}
}
Documentation
1. Dartdoc Comments
Write comprehensive dartdoc for public APIs:
/// A widget that displays awesome content.
///
/// The [AwesomeWidget] takes a [title] and optional [onTap] callback.
///
/// Example:
/// ```dart
/// AwesomeWidget(
/// title: 'Hello World',
/// onTap: () => print('Tapped!'),
/// )
/// ```
///
/// See also:
/// * [AnotherWidget], which provides similar functionality.
class AwesomeWidget extends StatelessWidget {
/// Creates an [AwesomeWidget].
///
/// The [title] must not be null.
const AwesomeWidget({
super.key,
required this.title,
this.onTap,
});
/// The title to display.
final String title;
/// Called when the widget is tapped.
final VoidCallback? onTap;
}
2. README.md
Include these sections:
# Package Name
Brief description
## Features
- Feature 1
- Feature 2
## Getting Started
Installation instructions
## Usage
Code examples
## Additional Information
Contributing guidelines, etc.
Testing
1. Test Coverage
Aim for >80% coverage:
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
2. Unit Tests
Test all business logic:
void main() {
group('AwesomeService', () {
test('calculates correctly', () {
final service = AwesomeService();
expect(service.calculate(2, 3), equals(5));
});
test('handles edge cases', () {
final service = AwesomeService();
expect(() => service.calculate(null, 3), throwsArgumentError);
});
});
}
3. Widget Tests
Test UI components:
testWidgets('AwesomeWidget displays title', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: AwesomeWidget(title: 'Test'),
),
);
expect(find.text('Test'), findsOneWidget);
});
Performance
1. Use Const Constructors
// ✅ Good - uses const
const Text('Hello')
// ❌ Bad - creates new instance each rebuild
Text('Hello')
2. Avoid Rebuilding Unnecessarily
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
Widget build(BuildContext context) {
// ✅ Good - extract expensive widgets to const
return const Column(
children: [
_Header(),
_Footer(),
],
);
}
}
class _Header extends StatelessWidget {
const _Header();
// ...
}
3. Lazy Loading
// ✅ Good - lazy initialization
late final ExpensiveObject _object = ExpensiveObject();
// Use ListView.builder for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)
Dependencies
1. Minimize Dependencies
Only include necessary dependencies:
# ✅ Good - minimal dependencies
dependencies:
flutter:
sdk: flutter
# ❌ Bad - unnecessary dependencies
dependencies:
flutter:
sdk: flutter
lodash: ^4.17.21
moment: ^2.29.4
2. Version Constraints
Use appropriate version constraints:
dependencies:
# ✅ Good - allows compatible updates
http: ^1.0.0
# ❌ Bad - too strict
http: 1.0.0
# ❌ Bad - too loose
http: any
Platform Support
1. Specify Supported Platforms
# pubspec.yaml
flutter:
plugin:
platforms:
android:
package: com.brewkits.awesome
pluginClass: AwesomePlugin
ios:
pluginClass: AwesomePlugin
web:
pluginClass: AwesomePluginWeb
2. Test on All Platforms
# Test on different platforms
flutter test
flutter build apk
flutter build ios
flutter build web
Continuous Integration
Example GitHub Actions
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
- uses: codecov/codecov-action@v3
Resources
Follow these practices to build packages that the community loves!