Skip to content

nikaera/flutter_hooks_lint

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

26 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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! 🦜

  • You can keep code that follows the rules outlined in the official documentation of flutter_hooks. βš“
  • A lint rules are available to improve both performance and readability. ✨
  • A lint rules supporting hooks_riverpod has been prepared. πŸ§‘β€πŸ€β€πŸ§‘

The currently available lint rules are as follows:

LintRule Description Quickfix
hooks_avoid_nesting You should use Hooks only inside the build method of a Widget.
hooks_avoid_within_class Hooks must not be defined within the class.
hooks_name_convention DO always prefix your hooks with use, https://pub.dev/packages/flutter_hooks#rules. βœ…
hooks_extends Using Hooks inside a Widget other than HookWidget or HookConsumerWidget will result in an error at runtime. βœ…
hooks_unuse_widget If you are not using Hooks inside of a Widget, you do not need HookWidget or HookConsumerWidget. βœ…
hooks_memoized_consideration Considering performance and functionality, there may be places where it is worth considering the use of useMemoized. βœ…
hooks_callback_consideration There 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 HooksWidget {
  @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 Melos ( dart pub global activate melos )
  4. Set up the project and run the test ( melos bs )
  5. Add a test each time you modify
  6. 4. it is possible to check the operation by executing the command ( melos bs )
  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! πŸŽ‰