Improving Code Quality in Flutter with very_good_analysis

Flutter Jun 20, 2023

As Flutter developers, we want to write clean and maintainable code that follows best practices. One way to achieve this is by using tools that analyze our code and help us identify potential issues. While Flutter has its own set of rules to check our code, there are additional packages that can help us improve code quality even further. In this post, we will explore the very_good_analysis package and how it can make our Flutter projects better.

Key Features of very_good_analysis

  1. Formatting Rules: very_good_analysis helps us keep our code clean and readable by suggesting consistent formattings, like indentation and spacing.
  2. Naming Conventions: The package suggests how we should name our variables, functions, and classes to make our code clearer and more consistent.
  3. Code Complexity: The package identifies complex code structures, long methods, and large classes, helping us simplify our code and make it easier to understand.
  4. Unused Code Detection: The package points out any code that we are not using, so we can remove it and reduce clutter.
  5. Best Practices: The package gives us suggestions based on Flutter's best practices, such as avoiding certain types and using parameters effectively.

Setting up very_good_analysis

To use very_good_analysis, we need to add it as a "dev dependency" in our project's pubspec.yaml file. We can do this by executing the following command: flutter pub add very_good_analysis --dev.

After adding the package, your pubspec.yaml file should have the updated dev_dependencies section similar to the following:

dev_dependencies:
  flutter_test:
    sdk: flutter
  very_good_analysis: ^5.0.0+1

Now that we have the package installed, we need to change the analysis_options.yaml file to include the package, see the example below:

include: package:very_good_analysis/analysis_options.yaml

In the above example, we included the package's analysis_options.yaml this will ensure that we always use the latest version of their lints. However, it is possible to specify a version:

include: package:very_good_analysis/analysis_options.5.0.0.yaml

By including the version, you ensure that your project consistently uses the desired version of very_good_analysis and its associated lint rules.

Now that we have properly set up our analysis_options.yaml file, let us go through some examples.

Flutter analyze

To demonstrate what issues our analyzer will pick up, we will use the following main.dart file. This file has a lot of issues on purpose, see below:

import 'dart:math';

void main() {
  int myVariable= 10;
  print('The value of myVariable is $myVariable');


  void complexMethod() {
    for (int i = 0; i < 100; i++) {
      if (i % 2 == 0) {
        print(i);
      }
    }
  }

  void calculateTotalPrice(double price, int quantity) {
    dynamic total = price * quantity;
    print('Total price: $total');
  }
}

class myClass {
  late String first_name;

  void displayMessage() {
    print('Hello, World!');
  }
}

Once we run flutter analyze we get the following linting errors:

Analyzing codeonwards_demo...                                           

warning - Unused import: 'dart:math' - lib\main.dart:1:8 - unused_import           
   info - Local variables should be final - lib\main.dart:4:3 - prefer_final_locals
   info - Unnecessary type annotation on a local variable - lib\main.dart:4:3 - omit_local_variable_types
   info - Don't invoke 'print' in production code - lib\main.dart:5:3 - avoid_print
   info - The declaration 'complexMethod' isn't referenced - lib\main.dart:8:8 - unused_element
   info - Unnecessary type annotation on a local variable - lib\main.dart:9:10 - omit_local_variable_types
   info - Use 'isEven' rather than '% 2' - lib\main.dart:10:11 - use_is_even_rather_than_modulo
   info - Don't invoke 'print' in production code - lib\main.dart:11:9 - avoid_print
   info - The declaration 'calculateTotalPrice' isn't referenced - lib\main.dart:16:8 - unused_element
   info - Local variables should be final - lib\main.dart:17:5 - prefer_final_locals
   info - Don't invoke 'print' in production code - lib\main.dart:18:5 - avoid_print
   info - Missing documentation for a public member - lib\main.dart:22:7 - public_member_api_docs
   info - The type name 'myClass' isn't an UpperCamelCase identifier - lib\main.dart:22:7 - camel_case_types
   info - Missing documentation for a public member - lib\main.dart:23:15 - public_member_api_docs
   info - The variable name 'first_name' isn't a lowerCamelCase identifier - lib\main.dart:23:15 - non_constant_identifier_names
   info - Missing documentation for a public member - lib\main.dart:25:8 - public_member_api_docs
   info - Don't invoke 'print' in production code - lib\main.dart:26:5 - avoid_print
   info - Missing a newline at the end of the file - lib\main.dart:28:2 - eol_at_end_of_file
   
18 issues found. (ran in 1.0s)

As you can see there are quite a lot of issues and they might be daunting at first, but they are easy to solve. And if you are using an IDE it will highlight the issues. Also as you can see the messages are self-explanatory and they even show the file and the line.

Now that we know which issues we have, let us address them, see the improved code:

// ignore_for_file: avoid_print

void main() {
  const myVariable = 10;
  print('The value of myVariable is $myVariable');


  void complexMethod() {
    for (var i = 0; i < 100; i++) {
      if (i.isEven) {
        print(i);
      }
    }
  }

  complexMethod();

  void calculateTotalPrice(double price, int quantity) {
    final total = price * quantity;
    print('Total price: $total');
  }

  calculateTotalPrice(5, 5);
}

/// My class
class MyClass {
  /// firstname
  late String firstName;

  /// displayMessage
  void displayMessage() {
    print('Hello, World!');
  }
}


To fix the issues we made the following changes:

  1. Unused Import: We removed the unused import statement for 'dart:math' since it wasn't being used in our code.
  2. Prefer Final Locals: We changed the declaration of myVariable from int myVariable = 10; to const myVariable = 10;. This change makes it a constant variable, indicating that its value will not change.
  3. Omit Local Variable Types: We removed the unnecessary type annotations on local variables. The explicit type annotations in int myVariable = 10; and for (int i = 0; i < 100; i++) were removed, allowing Dart's type inference to determine the types.
  4. Avoid Print: The analyzer suggested avoiding the use of print in production code. Since the print statements inside the main function were for demonstration purposes, we ignored the lint warning using // ignore_for_file: avoid_print.
  5. Unused Element: We invoked the unused functions complexMethod and calculateTotalPrice to resolve the warning.
  6. Use isEven Rather Than % 2: The analyzer recommended using the isEven property instead of the modulo operator % 2 in the if condition. We updated the condition from if (i % 2 == 0) to if (i.isEven).
  7. Missing Documentation and Naming Conventions: We addressed the missing documentation and naming convention issues for the MyClass class. We added documentation comments /// My class, /// firstname,   and /// displayMessage. We also changed the variable name first_name to firstName to follow the lowerCamelCase naming convention.
  8. EOL at End of File: The analyzer recommended adding a new line at the end of the file, so we added an empty line to meet this requirement.

These changes address the linting issues raised by the very_good_analysis package. Remember, if you are using an IDE, it will highlight these issues, making it easier for you to identify and resolve them.

Disabling specific rules

There are multiple ways to disable a specific rule, the rules can be disabled by line, by file, or project-wide.

1. Ignore a single line: Use the // ignore: comment to disable a specific rule for a single line of code. You can specify the rule name after the // ignore: comment. For example:

// ignore: avoid_print

2.  Ignore an entire file: Use the // ignore_for_file: comment to disable a specific rule for the entire file. You can specify the rule name after the // ignore_for_file: comment. For example:

// ignore_for_file: public_member_api_docs

3. Customize analysis options: In some cases, you may want to disable a rule globally for your project. To do this, you can customize the analysis_options.yaml file and configure the specific rule to be ignored. This allows you to disable a rule project-wide. For example:

include: package:very_good_analysis/analysis_options.yaml

linter:
  rules:
    public_member_api_docs: false

It is crucial to use these methods carefully and only turn off rules when absolutely necessary. Disabling rules should be done with care because they exist to help uphold code quality and follow recommended practices. It is advisable to document why a rule is being disabled to give context and clarity to yourself and/or your team.

Why use very_good_analysis?

The very_good_analysis package is a powerful tool that checks our Flutter code for issues and suggests improvements. It goes beyond the standard code checks provided by Flutter. By adding the package to our project, we can enforce best practices, make our code easier to read and catch problems early on.

Conclusion

The very_good_analysis package is a valuable tool for improving code quality in Flutter projects. By using it, you can ensure your code follows good practices, is easy to read, and has fewer potential issues. Adding very_good_analysis to your Flutter development workflow will help you maintain a high-quality and maintainable codebase. Give it a try and see the positive impact it can have on your Flutter projects.

Tags