Linting and analysis_options in Flutter: solved exercise
Linting and analysis_options in Flutter: solved exercise
Dart’s static analyzer catches errors, bad practices, and dead code before you run the app. A well-configured analysis_options.yaml is the first quality gate in any professional project.
Problem statement
Configure analysis_options.yaml with:
flutter_lints rules as the base.
Additional production rules (prefer_final, avoid_print, etc.).
Analysis warnings treated as compile errors.
Exclusions for generated code.
Then fix the most common warnings in real Dart code.
Dependencies
1
2
3
4
dev_dependencies:flutter_lints:^5.0.0# Stricter alternative (optional):# very_good_analysis: ^7.0.0
# analysis_options.yaml (at project root)include:package:flutter_lints/flutter.yamlanalyzer:# Treat warnings as errors (recommended in CI)errors:invalid_annotation_target:ignore # required with Freezedmissing_required_param:errordead_code:warningunused_import:errorunused_local_variable:warningexclude:- "**/*.g.dart"# generated by build_runner / json_serializable- "**/*.freezed.dart"# generated by Freezed- "lib/generated/**"# generated i18n- "test/.test_coverage.dart"linter:rules:# ── Code quality ──────────────────────────────────────────────────────prefer_final_locals:true# var → final when not reassignedprefer_final_fields:true# immutable fields → finalprefer_const_constructors:true# use const wherever possibleprefer_const_literals_to_create_immutables:trueunnecessary_const:trueunnecessary_new:true# ── Safety and bugs ───────────────────────────────────────────────────avoid_print:true# use debugPrint or a loggeravoid_dynamic_calls:true# no .call() on dynamicalways_use_package_imports:false# relative imports OK within same packagecancel_subscriptions:true# StreamSubscription.cancel()close_sinks:true# Sink.close()unawaited_futures:true# await all relevant Futures# ── Style ─────────────────────────────────────────────────────────────directives_ordering:true# sorted importssort_pub_dependencies:false# pub.dev sorts them automaticallylines_longer_than_80_chars:false# dart fmt handles thisflutter_style_todos: true # TODO(name):description# ── Nullability ───────────────────────────────────────────────────────prefer_null_aware_operators:trueavoid_null_checks_in_equality_operators:truenull_closures:true# ── Performance ───────────────────────────────────────────────────────avoid_unnecessary_containers:true# no unnecessary Containeruse_colored_box:true# Container(color:) → ColoredBoxuse_decorated_box:true# Container(decoration:) → DecoratedBox
Examples: code with warning and its fix
1. avoid_print
1
2
3
4
5
6
7
8
9
// ❌ Warning: avoid_print
voidfetchData(){print('Loading data...');}// ✅ Correct
voidfetchData(){debugPrint('Loading data...');// only in debug mode
}
2. prefer_final_locals
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ Warning: prefer_final_locals
voidprocessUser(Map<String,dynamic>json){varname=json['name']asString;// name is never reassigned
varage=json['age']asint;debugPrint('$name, $age');}// ✅ Correct
voidprocessUser(Map<String,dynamic>json){finalname=json['name']asString;finalage=json['age']asint;debugPrint('$name, $age');}
3. use_colored_box
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ Warning: use_colored_box
Widgetbuild(BuildContextcontext){returnContainer(color:Colors.red,child:constText('Hello'),);}// ✅ Correct (more efficient, no extra layout layer)
Widgetbuild(BuildContextcontext){returnconstColoredBox(color:Colors.red,child:Text('Hello'),);}
4. unawaited_futures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ Warning: unawaited_futures
voidonButtonTap(){saveToDatabase();// unawaited Future
}// ✅ Correct
Future<void>onButtonTap()async{awaitsaveToDatabase();}// ✅ Alternative if intentional:
voidonButtonTap(){unawaited(saveToDatabase());// import 'dart:async'
}
5. cancel_subscriptions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ❌ Warning: cancel_subscriptions
class_MyWidgetStateextendsState<MyWidget>{lateStreamSubscription<int>_sub;@overridevoidinitState(){super.initState();_sub=stream.listen((_){});}// dispose does not cancel _sub → memory leak
}// ✅ Correct
@overridevoiddispose(){_sub.cancel();super.dispose();}
Running the analyzer from the terminal
1
2
3
4
5
6
7
8
# Full project analysisdart analyze
# Errors only (ignores warnings)dart analyze --fatal-warnings
# Auto-fix simple issuesdart fix --apply
very_good_analysis is stricter than flutter_lints and enables ~200 rules. It is the option used by Very Good Ventures in production projects. If you are starting a new project with high standards, start with it:
Silencing all warnings with // ignore:: inline ignores should be the exception, not the rule. If a warning appears in many places, evaluate whether the rule suits your project and adjust analysis_options.yaml.
Not excluding generated files: *.g.dart and *.freezed.dart generate hundreds of false warnings if not excluded.
Adding --fatal-warnings in CI without cleaning warnings first: the pipeline fails from the first commit. Fix all warnings before enabling it.
Practical use
A project with strict linting from day 1 catches bugs before they reach review, standardizes style without PR debates, and makes onboarding faster because the code is predictable.