xlints
xlints is a Flutter lint package focused on widget and logic performance.
Available Rules
xlints_prefer_const_constructors
Detects widgets that can be const but are not marked const.
BAD:
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.star),
);
}
GOOD:
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.star),
);
}
xlints_prefer_listview_builder
Detects long lists that should use .builder.
BAD:
ListView(
children: List.generate(1000, (i) => Text('Item $i')),
)
GOOD:
ListView.builder(
itemCount: 1000,
itemBuilder: (_, i) => Text('Item $i'),
)
xlints_avoid_listview_with_children
Detects ListView/GridView(children: ...) usage that builds all items eagerly.
BAD:
GridView(
children: List.generate(200, (i) => Card(child: Text('$i'))),
)
GOOD:
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: 200,
itemBuilder: (_, i) => Card(child: Text('$i')),
)
xlints_avoid_opacity_widget
Detects Opacity usage in cases where a cheaper alternative is preferred.
BAD:
Opacity(
opacity: 0.5,
child: Image.network(url),
)
GOOD:
AnimatedOpacity(
opacity: 0.5,
duration: const Duration(milliseconds: 200),
child: Image.network(url),
)
xlints_avoid_padding_wrapping_margin_widget
Detects Padding wrapping a child that already uses margin.
BAD:
Padding(
padding: const EdgeInsets.all(16),
child: Container(
margin: const EdgeInsets.all(8),
child: const Text('Hello'),
),
)
GOOD:
Container(
margin: const EdgeInsets.all(24),
child: const Text('Hello'),
)
xlints_avoid_shrink_wrap_true
Detects shrinkWrap: true on list/grid widgets, which can increase layout cost.
BAD:
ListView(
shrinkWrap: true,
children: items.map((e) => Text(e)).toList(),
)
GOOD:
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (_, i) => Text(items[i]),
),
)
xlints_avoid_intrinsic_widgets
Detects expensive IntrinsicHeight/IntrinsicWidth usage.
BAD:
IntrinsicHeight(
child: Row(children: children),
)
GOOD:
SizedBox(
height: 72,
child: Row(children: children),
)
xlints_avoid_controller_in_build
Detects controller/node creation inside build().
BAD:
@override
Widget build(BuildContext context) {
final controller = ScrollController();
return ListView(controller: controller);
}
BAD:
@override
Widget build(BuildContext context) {
final focusNode = FocusNode();
return TextField(focusNode: focusNode);
}
GOOD:
class _MyState extends State<MyWidget> {
late final ScrollController controller;
@override
void initState() {
super.initState();
controller = ScrollController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView(controller: controller);
}
}
xlints_avoid_widget_operator_equals
Detects operator == override on Widget subclasses.
BAD:
class MyCard extends StatelessWidget {
@override
bool operator ==(Object other) => identical(this, other);
@override
int get hashCode => 0;
@override
Widget build(BuildContext context) => const SizedBox();
}
GOOD:
class MyCard extends StatelessWidget {
const MyCard({super.key});
@override
Widget build(BuildContext context) => const SizedBox();
}
xlints_avoid_set_state_in_build
Detects setState() calls inside build().
BAD:
@override
Widget build(BuildContext context) {
setState(() => counter++);
return Text('$counter');
}
GOOD:
void _increment() {
setState(() => counter++);
}
xlints_avoid_json_decode_in_build
Detects jsonDecode usage inside build().
BAD:
@override
Widget build(BuildContext context) {
final data = jsonDecode(rawJson);
return Text('${data['title']}');
}
GOOD:
late final Map<String, dynamic> data;
@override
void initState() {
super.initState();
data = jsonDecode(rawJson) as Map<String, dynamic>;
}
xlints_avoid_heavy_sync_work_in_build
Detects heavy synchronous collection work in build().
BAD:
@override
Widget build(BuildContext context) {
final values = items.toList()..sort();
return Text('${values.first}');
}
GOOD:
late final List<int> sorted;
@override
void initState() {
super.initState();
sorted = items.toList()..sort();
}
xlints_prefer_final_locals
Detects local var variables that are never reassigned.
BAD:
void logValue() {
var message = 'ready';
print(message);
}
GOOD:
void logValue() {
final message = 'ready';
print(message);
}
xlints_avoid_recreating_regexp
Detects repeated RegExp(...) creation in loops/build.
BAD:
for (final item in items) {
final r = RegExp(r'\d+');
if (r.hasMatch(item)) {}
}
GOOD:
final r = RegExp(r'\d+');
for (final item in items) {
if (r.hasMatch(item)) {}
}
xlints_avoid_list_contains_in_large_loops
Detects list.contains(...) calls inside loops.
BAD:
for (final id in ids) {
if (selectedIds.contains(id)) {}
}
GOOD:
final selectedSet = selectedIds.toSet();
for (final id in ids) {
if (selectedSet.contains(id)) {}
}
xlints_avoid_repeated_datetime_now_in_loop
Detects repeated DateTime.now() calls inside loops.
BAD:
for (var i = 0; i < 100; i++) {
final now = DateTime.now();
consume(now);
}
GOOD:
final now = DateTime.now();
for (var i = 0; i < 100; i++) {
consume(now);
}
xlints_prefer_collection_if_spread_over_temp_lists
Detects temporary list accumulation patterns better expressed with collection literals.
BAD:
final widgets = <Widget>[];
if (showHeader) widgets.add(const Text('Header'));
if (items.isNotEmpty) widgets.addAll(items.map(Text.new));
GOOD:
final widgets = <Widget>[
if (showHeader) const Text('Header'),
if (items.isNotEmpty) ...items.map(Text.new),
];
xlints_prefer_string_buffer
Detects string concatenation with + inside loops.
BAD:
var result = '';
for (var i = 0; i < 1000; i++) {
result = result + values[i];
}
GOOD:
final buffer = StringBuffer();
for (var i = 0; i < 1000; i++) {
buffer.write(values[i]);
}
final result = buffer.toString();
Prerequisites
- Flutter SDK available in your environment.
- Dart SDK compatible with this package (
sdk: ^3.10.4).
Installation
Option A: from pub.dev
Add this to dev_dependencies:
dev_dependencies:
xlints: ^1.0.0
Run:
flutter pub get
Option B: from a local path (for local development)
dev_dependencies:
xlints:
path: ../path-to-xlints
Run:
flutter pub get
Configure analysis_options.yaml
Option 1: Fresh project
Use the provided xlints config:
include: package:xlints/analysis_options_xlints.yaml
This enables the custom_lint plugin.
Option 2: Project already using flutter_lints (recommended for existing projects)
If your project already uses flutter_lints or another lint config, keep your current include: and add plugins: [custom_lint] under the analyzer section.
Before (your current config):
include: package:flutter_lints/flutter.yaml
linter:
rules:
analyzer:
errors:
invalid_annotation_target: ignore
enable-experiment:
- dot-shorthands
After (add xlints):
include: package:flutter_lints/flutter.yaml
linter:
rules:
analyzer:
errors:
invalid_annotation_target: ignore
enable-experiment:
- dot-shorthands
plugins:
- custom_lint
The only change is adding plugins: [custom_lint] under analyzer. Your existing rules, errors, and experiments stay as-is.
Notes for both options
- You do not need to add
custom_lintmanually topubspec.yaml; xlints bundles it. - You do not need to add
analyzer.pluginsmanually if you use Option 1 (include xlints config).
Run Lints
Run from your Flutter app root:
dart run custom_lint
Alternative:
flutter pub run custom_lint
To auto-apply available fixes:
dart run custom_lint --fix
IDE Integration
- Make sure
analysis_options.yamlis configured correctly. - Reopen the project if warnings do not appear.
- Use lightbulb Quick Fix for rules that provide fixes.
Rules With Quick Fix (Current)
xlints_prefer_const_constructors: adds theconstkeyword.xlints_avoid_padding_wrapping_margin_widget: removes the outerPadding.
Rule Configuration
Disable specific rules by adding a custom_lint section to your analysis_options.yaml:
custom_lint:
rules:
- xlints_prefer_const_constructors: false
- xlints_avoid_opacity_widget: false
Works with both Option 1 (xlints-only) and Option 2 (flutter_lints + xlints).
Example Usage
An example project is in the example/ directory in the GitHub repo (it is not published to pub.dev so that pub.dev scoring does not hit analyzer plugin errors).
Run from the repo:
cd example
dart run custom_lint
The example intentionally contains bad patterns to trigger all xlints rules.
Troubleshooting
include_file_not_found for package:xlints/analysis_options_xlints.yaml
Common causes:
flutter pub gethas not been run.xlintsis not present in your dependency graph.- Incorrect local path dependency.
Checklist:
- Ensure
pubspec.yamlincludesxlintsindev_dependencies. - Run
flutter pub getagain. - Ensure the include line is exactly:
include: package:xlints/analysis_options_xlints.yaml - Run
dart run custom_lintagain.
Rules do not appear in IDE, but appear in CLI
- Reload the IDE window or restart the analysis server.
- Make sure the opened project root contains
analysis_options.yaml.
Internal Dependencies
analyzer: ^8.4.0custom_lint: ^0.8.1custom_lint_builder: ^0.8.1