flutter_hooks_lint 1.4.0

SDKflutter
Platformandroidioswindowslinuxmacos

A lint package providing guidelines for using flutter_hooks in your Flutter widget!

flutter_hooks_lint🪝🏴‍☠️

flutter_hooks_lint package very_good_analysis Build Status

A lint package providing guidelines for using flutter_hooks in your Flutter widget! 🦜

The currently available lint rules are as follows:

LintRuleDescriptionQuickfix
hooks_avoid_nestingYou should use Hooks only inside the build method of a Widget.
hooks_avoid_within_classHooks must not be defined within the class.
hooks_name_conventionDO always prefix your hooks with use, https://pub.dev/packages/flutter_hooks#rules.
hooks_extendsUsing Hooks inside a Widget other than HookWidget or HookConsumerWidget will result in an error at runtime.
hooks_unuse_widgetIf you are not using Hooks inside of a Widget, you do not need HookWidget or HookConsumerWidget.
hooks_memoized_considerationConsidering performance and functionality, there may be places where it is worth considering the use of useMemoized.
hooks_callback_considerationThere are cases where you can use useCallback, which is the syntax sugar for useMemoized.

screencast

Installation

Add both flutter_hooks_lint and custom_lint to your pubspec.yaml:

dev_dependencies:
  custom_lint:
  flutter_hooks_lint:

Enable custom_lint's plugin in your analysis_options.yaml:

analyzer:
  plugins:
    - custom_lint

Enabling/disabling lints

By default when installing flutter_hooks_lint, most of the lints will be enabled.

You may dislike one of the various lint rules offered by flutter_hooks_lint. In that event, you can explicitly disable this lint rule for your project by modifying the analysis_options.yaml

analyzer:
  plugins:
    - custom_lint

custom_lint:
  rules:
    # Explicitly disable one lint rule
    - hooks_unuse_widget: false

Running flutter_hooks_lint in the terminal/CI 🤖

Custom lint rules created by flutter_hooks_lint may not show-up in dart analyze. To fix this, you can run a custom command line: custom_lint.

Since your project should already have custom_lint installed, then you should be able to run:

# Install custom_lint for project
dart pub get custom_lint
# run custom_lint's command line in a project
dart run custom_lint

Alternatively, you can globally install custom_lint:

# Install custom_lint for all projects
dart pub global activate custom_lint
# run custom_lint's command line in a project
custom_lint

All the lints

hooks_avoid_nesting

You should use Hooks only inside the build method of a Widget.

Bad:

@override
Widget build(BuildContext context) {
  if (isEnable) {
    final state = useState(0); // ❌
    return Text(state.value.toString());
  } else {
    return SizedBox.shrink();
  }
}

Good:

@override
Widget build(BuildContext context) {
    final state = useState(0); // ⭕
    return isEnable ?
      Text(state.value.toString()) :
      SizedBox.shrink();
}

hooks_avoid_within_class

Defining Custom Hooks within a class mixes the characteristics of class and method, leading to potentially complex code.

Bad

class TestHelper {
  const TestHelper._();

  static void useEffectOnce(Dispose? Function() effect) { // ❌
    useEffect(effect, const []);
  }
}

Good:

void useEffectOnce(Dispose? Function() effect) { // ⭕
  useEffect(effect, const []);
}

hooks_name_convention

DO always prefix your hooks with use.

Bad:

class WrongMethodWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    effectOnce(() { // ❌
      return;
    });
    return Text('');
  }
}

Good:

class CorrectMethodWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    useEffectOnce(() { // ⭕
      return;
    });
    return Text('');
  }
}

hooks_extends

Using Hooks inside a Widget other than HookWidget or HookConsumerWidget will result in an error at runtime.

Bad:

class RequiresHookWidget extends StatelessWidget { // ❌
  @override
  Widget build(BuildContext context) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

class RequiresConsumerHookWidget extends ConsumerWidget { // ❌
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

Good:

class RequiresHookWidget extends HookWidget { // ⭕
  @override
  Widget build(BuildContext context) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

class RequiresConsumerHookWidget extends HookConsumerWidget { // ⭕
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

hooks_unuse_widget

If you are not using Hooks inside of a Widget, you do not need HookWidget or HookConsumerWidget.

Bad:

class UnuseHookWidget extends HookWidget { // ❌
  @override
  Widget build(BuildContext context) {
    return SizedBox.shrink();
  }
}

class UnuseHookConsumerWidget extends HookConsumerWidget { // ❌
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return SizedBox.shrink();
  }
}

Good:

class UnuseHookWidget extends StatelessWidget { // ⭕
  @override
  Widget build(BuildContext context) {
    return SizedBox.shrink();
  }
}

class UnuseHookConsumerWidget extends ConsumerWidget { // ⭕
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return SizedBox.shrink();
  }
}

hooks_memoized_consideration

Considering functionality, there may be places where it is worth considering the use of useMemoized.

Bad

class ConsiderationMemoizedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final key = GlobalKey<TooltipState>(); // ❌
    final objectKey = GlobalObjectKey<TooltipState>("object"); // ❌
    return Column(
        children: [key, objectKey]
            .map((k) => Tooltip(
                  key: key,
                  message: 'Click me!',
                ))
            .toList());
  }
}

Good

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final key = useMemoized(() => GlobalKey<TooltipState>()); // ⭕
    final objectKey = useMemoized(() => GlobalObjectKey<TooltipState>("object")); // ⭕
    return Column(
        children: [key, objectKey]
            .map((k) => Tooltip(
                  key: key,
                  message: 'Click me!',
                ))
            .toList());
  }
}

hooks_callback_consideration

There are cases where you can use useCallback, which is the syntax sugar for useMemoized.

Bad

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final state = useMemoized(() => () => 0); // ❌
    return Text(state.call().toString());
  }
}

Good

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final state = useCallback(() => 0); // ⭕
    return Text(state.call().toString());
  }
}

Contribution 🎁

Thanks for your interest! Issues and PR are welcomed! 🙌 I would be delighted if you could translate the documentation into natural English or add new lint rules!

The project setup procedures for development are as follows:

  1. Fork it ( https://github.com/nikaera/flutter_hooks_lint/fork )
  2. Create your fix/feature branch (git checkout -b my-new-feature)
  3. Install packages ( flutter pub get )
  4. Run the test ( dart run grinder )
  5. Add a test each time you modify
  6. 4. it is possible to check the operation by executing the command ( dart run grinder )
  7. Commit your changes (git commit -am 'Add some feature')
  8. Push to the branch (git push origin my-new-feature)
  9. Create new Pull Request! 🎉