Sangria Lints
Custom lints wanted for personal developments.
Table of content
Getting started
Add Sangria_lints
Add sangria_lints to your pubspec.yaml
:
dev_dependencies:
sangria_lints:
Enable custom_lint
sangria_lints comes bundled with its own rules using custom_lints.
-
Add both sangria_lints and custom_lint to your
pubspec.yaml
:dev_dependencies: sangria_lints: custom_lint: # <- add this
-
Enable
custom_lint
's plugin in youranalysis_options.yaml
:analyzer: plugins: - sangria_lints
Disabling lint rules
By default when installing sangria_lints, all the lints will be enabled. To change this, you have a few options.
analyzer:
plugins:
- custom_lint
custom_lint:
rules:
# Explicitly disable one custom-lint rule.
- use_setstate_synchronously: false
All custom-lint rules in sangria_lints
use_setstate_synchronously
A use_setstate_synchronously
rule that discourages the use of setState across asynchronous gaps within subclasses of State.
In async functions, the state of a widget may have been disposed across asynchronous gaps in a case when the user moves to a different screen. This leads to setState() called after dispose()
error.
Since widgets can be unmounted before a Future gets resolved, seeing if widgets are mounted is necessary before calling setState.
❌ BAD
class _MyWidgetState extends State<MyWidget> {
String message;
@override
Widget build(BuildContext context) {
return Button(
onPressed: () async {
String fromSharedPreference = await getFromSharedPreference();
// LINT: Avoid calling 'setState' across asynchronous gaps without seeing if the widget is mounted.
setState(() {
message = fromSharedPreference;
});
},
child: Text(message),
);
}
}
✅ GOOD
class _MyWidgetState extends State<MyWidget> {
String message;
@override
Widget build(BuildContext context) {
return Button(
onPressed: () async {
String fromSharedPreference = await getFromSharedPreference();
if (mounted) {
setState(() {
message = fromSharedPreference;
});
}
},
child: Text(message),
);
}
}
avoid_empty_container
A avoid_empty_container
rule that discourages the use of empty container.
Since Container
generates many properties like padding, margin, decoration, and constraints, it is perfered to use SizedBox
instead.
❌ BAD
class CustomText extends StatelessWidget {
bool canDisplay;
@override
Widget build(BuildContext context) {
return canDisplay ? Text('canDisplay') : Container();
}
}
✅ GOOD
class CustomText extends StatelessWidget {
bool canDisplay;
@override
Widget build(BuildContext context) {
return canDisplay ? Text('canDisplay') : SizedBox.shrink();
}
}
no_disabled_tests
A no_disabled_tests
rule that raises a warning about disabled tests inspired by no-disabled-tests rule in plugins for Jest and Vitest.
Test and flutter_test package have a feature that allows you to temporarily mark tests as disabled. This feature is often helpful, however before committing changes we may want to check that all tests are running.
❌ BAD
void main() {
group('test group', () {
test('test', () {}, skip: true);
testWidgets('test widgets', (tester) async {}, skip: true);
}, skip: true)
}
✅ GOOD
void main() {
group('test group', () {
test('test', () {});
testWidgets('test widgets', (tester) async {});
})
}
use_widget_ref_synchronously
A use_widget_ref_synchronously
rule that discourages the use of WidgetRef across asynchronous gaps within Consumer widgets provided by Riverpod.
In async functions, a widget may have been disposed across asynchronous gaps in a case when the user got back to previous screen. This leads to Cannot use ref after the widget was disposed called
error.
Since widgets can be unmounted before a Future gets resolved, seeing if widgets are mounted is necessary before using WidgetRef.
Riverpod recommends seeing if widgets are mounted to fix the error as well.
❌ BAD
ElevatedButton(
onPressed: () async {
await future;
ref.read(...); // May throw "Cannot use "ref" after the widget was disposed"
}
)
✅ GOOD
ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // No longer throws
}
)