xlints 1.0.5 copy "xlints: ^1.0.5" to clipboard
xlints: ^1.0.5 copied to clipboard

Flutter performance linter that detects costly code patterns and provides actionable optimization guidance.

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.

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_lint manually to pubspec.yaml; xlints bundles it.
  • You do not need to add analyzer.plugins manually 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.yaml is 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 the const keyword.
  • xlints_avoid_padding_wrapping_margin_widget: removes the outer Padding.

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 get has not been run.
  • xlints is not present in your dependency graph.
  • Incorrect local path dependency.

Checklist:

  1. Ensure pubspec.yaml includes xlints in dev_dependencies.
  2. Run flutter pub get again.
  3. Ensure the include line is exactly: include: package:xlints/analysis_options_xlints.yaml
  4. Run dart run custom_lint again.

Rules do not appear in IDE, but appear in CLI #

  1. Reload the IDE window or restart the analysis server.
  2. Make sure the opened project root contains analysis_options.yaml.

Internal Dependencies #

  • analyzer: ^8.4.0
  • custom_lint: ^0.8.1
  • custom_lint_builder: ^0.8.1
3
likes
130
points
226
downloads

Documentation

API reference

Publisher

verified publisheranonimeact.com

Weekly Downloads

Flutter performance linter that detects costly code patterns and provides actionable optimization guidance.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

analyzer, analyzer_plugin, custom_lint, custom_lint_builder

More

Packages that depend on xlints