Lint Rules Overview
Flutter Compositions provides custom lint rules to enforce best practices and prevent common mistakes. This guide provides a quick overview of all available rules and how to use them.
Quick Start
Installation
Add to your pubspec.yaml:
dev_dependencies:
flutter_compositions_lints: ^0.1.0Create or update analysis_options.yaml:
plugins:
flutter_compositions_lints:
path: .Lints are automatically surfaced by the Dart analysis server in your IDE.
All Lint Rules
Reactivity Rules
Rules that ensure proper reactive state management.
flutter_compositions_ensure_reactive_props
Severity: Warning
Ensures that widget properties are accessed through widget() in the setup() method to maintain reactivity.
// ❌ Bad - not reactive
@override
Widget Function(BuildContext) setup() {
final greeting = 'Hello, $name!'; // Direct access
return (context) => Text(greeting);
}
// ✅ Good - reactive
@override
Widget Function(BuildContext) setup() {
final props = widget();
final greeting = computed(() => 'Hello, ${props.value.name}!');
return (context) => Text(greeting.value);
}Lifecycle Rules
Rules that manage component lifecycle and resource cleanup.
flutter_compositions_no_async_setup
Severity: Error
Prevents setup() methods from being async. The setup function must synchronously return a builder function.
// ❌ Bad - async setup
@override
Future<Widget Function(BuildContext)> setup() async {
final data = await fetchData();
return (context) => Text(data);
}
// ✅ Good - use onMounted for async
@override
Widget Function(BuildContext) setup() {
final data = ref<String?>(null);
onMounted(() async {
data.value = await fetchData();
});
return (context) => Text(data.value ?? 'Loading...');
}flutter_compositions_controller_lifecycle
Severity: Warning
Ensures Flutter controllers are properly disposed using either use* helper functions or manual disposal in onUnmounted().
Detected controller types:
- ScrollController
- PageController
- TextEditingController
- TabController
- AnimationController
- VideoPlayerController
- WebViewController
// ❌ Bad - never disposed
@override
Widget Function(BuildContext) setup() {
final controller = ScrollController();
return (context) => ListView(controller: controller);
}
// ✅ Good - auto-disposed
@override
Widget Function(BuildContext) setup() {
final controller = useScrollController();
return (context) => ListView(controller: controller.raw); // .raw avoids unnecessary rebuilds
}
// ✅ Good - manually disposed
@override
Widget Function(BuildContext) setup() {
final controller = ScrollController();
onUnmounted(() => controller.dispose());
return (context) => ListView(controller: controller);
}flutter_compositions_no_conditional_composition
Severity: Error
Ensures composition API calls are not placed inside conditionals or loops. Similar to React Hooks rules.
Flagged composition APIs:
- Reactivity:
ref,computed,writableComputed,customRef,watch,watchEffect - Lifecycle:
onMounted,onUnmounted - Dependency injection:
provide,inject - Controllers:
useController,useScrollController,usePageController,useFocusNode,useTextEditingController,useValueNotifier,useAnimationController, etc.
// ❌ Bad - conditional composition
@override
Widget Function(BuildContext) setup() {
if (someCondition) {
final count = ref(0); // Don't do this!
}
return (context) => Text('Hello');
}
// ✅ Good - composition at top level
@override
Widget Function(BuildContext) setup() {
final count = ref(0);
// Conditional logic on values is OK
if (someCondition) {
count.value = 10;
}
return (context) => Text('${count.value}');
}Best Practices Rules
General best practice rules for clean code.
flutter_compositions_shallow_reactivity
Severity: Warning
Warns about shallow reactivity limitations. Direct mutations of object properties or array elements won't trigger reactive updates.
// ❌ Bad - direct mutation
items.value.add(4); // Won't trigger update!
user.value.name = 'Jane'; // Won't trigger update!
// ✅ Good - replace entire value
items.value = [...items.value, 4];
user.value = User(name: 'Jane');flutter_compositions_no_logic_in_builder
Severity: Warning
Prevents logic inside the builder function. Only props destructuring and return statements are allowed.
// ❌ Bad - logic in builder
return (context) {
final filtered = items.value.where(...).toList();
return ListView(children: filtered.map(ItemTile.new).toList());
};
// ✅ Good - logic in setup
final filtered = computed(() => items.value.where(...).toList());
return (context) => ListView(children: filtered.value.map(ItemTile.new).toList());flutter_compositions_prefer_raw_controller
Severity: Warning
Suggests using .raw instead of .value for controller refs in builder widget parameters to avoid unnecessary reactive tracking.
// ❌ Bad - subscribes unnecessarily
return (context) => ListView(controller: scrollController.value);
// ✅ Good - reads without tracking
return (context) => ListView(controller: scrollController.raw);Configuring Rules
Disable Rules in Code
Per File
// ignore_for_file: flutter_compositions_ensure_reactive_propsPer Line
// ignore: flutter_compositions_ensure_reactive_props
final name = this.name;Per Block
// ignore: flutter_compositions_controller_lifecycle
final controller = ScrollController();IDE Integration
Lints are powered by analysis_server_plugin and are automatically surfaced by the Dart analysis server. They appear in your IDE's diagnostics panel without running any extra commands.
VS Code
The built-in Flutter/Dart extensions surface diagnostics in the editor automatically, and quick fixes remain available via Ctrl/Cmd + ..
Android Studio / IntelliJ
Lints appear in the Problems panel automatically via the Dart analysis server.
Rule Summary Table
| Rule | Severity | Category | Auto-Fix | Description |
|---|---|---|---|---|
| ensure_reactive_props | Warning | Reactivity | No | Access props via widget() |
| no_async_setup | Error | Lifecycle | No | Prevent async setup methods |
| controller_lifecycle | Warning | Lifecycle | No | Ensure controller disposal |
| no_conditional_composition | Error | Lifecycle | No | Prevent conditional composition APIs |
| shallow_reactivity | Warning | Best Practices | No | Warn about shallow reactivity |
| no_logic_in_builder | Warning | Best Practices | No | No logic in builder function |
| prefer_raw_controller | Warning | Best Practices | No | Use .raw for controllers in builders |
Common Patterns
Pattern 1: Reactive Props
Always use widget() to access props reactively:
class UserCard extends CompositionWidget {
final String name;
@override
Widget Function(BuildContext) setup() {
final props = widget();
final greeting = computed(() => 'Hello, ${props.value.name}!');
return (context) => Text(greeting.value);
}
}Pattern 2: Controller Management
Always use composables for controllers:
@override
Widget Function(BuildContext) setup() {
// ✅ Auto-disposed
final scrollController = useScrollController();
final (textController, text, _) = useTextEditingController();
final (animController, animValue) = useAnimationController(
duration: Duration(seconds: 1),
);
return (context) => /* ... */;
}Pattern 3: Async Initialization
Use onMounted() for async operations:
@override
Widget Function(BuildContext) setup() {
final data = ref<String?>(null);
final isLoading = ref(false);
onMounted(() async {
isLoading.value = true;
try {
data.value = await fetchData();
} finally {
isLoading.value = false;
}
});
return (context) => /* ... */;
}Pattern 4: Type-Safe DI
Use InjectionKey for type-safe dependency injection:
class ThemeServiceKey extends InjectionKey<ThemeService> {
const ThemeServiceKey();
}
const themeServiceKey = ThemeServiceKey();
// Provide
provide(themeServiceKey, ThemeService());
// Inject
final theme = inject(themeServiceKey);Troubleshooting
Lints not showing up?
- Make sure
flutter_compositions_lintsis indev_dependencies - Ensure
analysis_options.yamlhas the plugin configured - Run
flutter pub get - Restart your IDE / analysis server
False positives?
- Check if the lint rule is correctly identifying the issue
- Use
// ignore:comments for intentional exceptions - Report false positives on GitHub Issues
Best Practices
- Enable all rules by default - Start strict, disable selectively
- Fix lints before committing - Keep your codebase clean
- Use IDE integration - Catch issues as you type
- Document exceptions - Always explain
// ignore:comments - Run in CI/CD - Enforce rules in your pipeline
Contributing
Found a false positive or have suggestions for new rules?
- Check existing issues on GitHub
- Open a new issue with:
- Code sample that triggers the lint
- Expected behavior
- Actual behavior
- Submit a PR with:
- Rule implementation
- Tests
- Documentation
See Also
- Complete Rules Reference — detailed documentation for each rule
- Reactivity Fundamentals — learn reactive patterns
- Best Practices — general best practices
- Built-in Composables — catalog of built-in helpers
Quick Reference
Must-follow Rules (Errors)
These rules prevent bugs and should never be disabled:
flutter_compositions_no_async_setup- Setup must be synchronousflutter_compositions_no_conditional_composition- Composition APIs must be called unconditionally
Recommended Rules (Warnings)
These rules enforce best practices:
flutter_compositions_ensure_reactive_props- Props must be reactiveflutter_compositions_controller_lifecycle- Controllers must be disposedflutter_compositions_shallow_reactivity- Warn about shallow reactivityflutter_compositions_no_logic_in_builder- Keep builder pureflutter_compositions_prefer_raw_controller- Use.rawfor controllers
For detailed documentation of each rule, see Complete Rules Reference.