Flutter 🐦

Hero image for Flutter 🐦

Project Structure

Generally speaking, a Flutter application can include multi-platforms codebase structure as a cross-platform framework. So it’s important to follow the corresponding project structure when it comes to a specific platform, for instances:

The following structure and directories must be followed for the core Flutter directories (lib/):

project-flutter
β”œβ”€ android/
β”œβ”€ ios/
β”œβ”€ web/
β”œβ”€ assets/
β”œβ”€ lib/
β”‚  β”œβ”€ di/
β”‚  β”œβ”€ api/
β”‚  β”œβ”€ l10n/
β”‚  β”œβ”€ helpers/
β”‚  β”œβ”€ models/
β”‚  β”œβ”€ presenters/
β”‚  β”œβ”€ repositories/
β”‚  β”œβ”€ preferences/
β”‚  β”œβ”€ usecases/
β”‚  β”œβ”€ views/
β”‚  β”œβ”€ main.dart
β”‚  └─ app.dart
β”œβ”€ integration_test/
β”œβ”€ test/
β”œβ”€ pubspec.yaml
β”œβ”€ Makefile
└─ README.md
  • assets: static assets (images, videos, fonts…) included for the application to use at the compile time.
  • android/, ios/, web/: containing all the platform specific configurations corresponding to Android, iOS, Web compilation.
  • lib: the core domain logics and presentation for the whole Flutter application. Depending on the choice of architecture, there could be more or less of the presenter directories added.
  • integration_test: contains all the integration test suites.
  • test: contains all the unit test suites.
  • pubspec.yaml: specifies dependencies that the project requires, such as particular packages (and their versions), fonts, or assets files (images, video…).
  • Makefile: use a Makefile to define executable CLI scripts.
  • README.md: contains all the prerequisites and setup guideline.

Development Environment

Code Editor

There is no specific editor is enforced at the team level. Flutter developers can pick what they are comfortable with but also what does not hinder their productivity.

The following options are the most widely used:

In additions, you will need:

  • XCode for iOS code inspection and editing.

Version Managers

Due to the diversity of projects the team works on, each application usually requires different versions of Flutter. Versions managers allow to install and switch between different versions effortlessly.

The following version manager(s) is recommended:

  • fvm is the preferred version manager for Flutter.

Formatting

Methods

  • Use soft-tabs with a two-space indent. For more indentation and spacing rules, follow the Official Dart Formatting rule.
  • Blank lines:
    • Separate each abstract method using a blank line for better readability.

      abstract class Human {
        void speak();
      
        void run();
      }
      
  • Line breaks:
    • Use the line break before each parameter in a method when it exceeds the line length.
    • Always use a line break for the method with more than two parameters.

      // Bad
      void createAccount(String firstName, String lastName, String email, String phoneNumber) {
        // Your code
      }
      
      // Good
      void createAccount(
        String firstName,
        String lastName,
        String email,
        String phoneNumber,
      ) {
        // Your code
      }
      
  • Commas: Prefer to add a trailing comma , when there are more than 2 parameters in a method for auto-formatting in VSCode or Android Studio. This applies to Widget also.

    // Bad
    void setReminder({String? label, DateTime? datetime, bool? enableSnooze}) {
      // Your code
    }
    
    // Good
    void setReminder({
      String? label,
      DateTime? datetime,
      bool? enableSnooze,
    }) {
      // Your code
    }
    
  • Keywords:
    • required: should be at the same line of the parameters.
      // Bad
      void disableButton({
        required
        Widget button,
        required
        Color color,
      })
      
      // Good
      void disableButton({
        required Widget button,
        required Color color,
      })
      

Comments

Widgets

  • Add const before the widget whenever possible. By adding this keyword, the constant widget is NOT re-rendered when rebuilding the parent widget, improving the application performance. Read further in this article The Flutter const keyword demystified.

    Center(
      child: const Text('Hello World!'),
    );
    
  • Prefer to split the code into small widgets to avoid large nested trees. The debugging process will become easy for checking the UI issue. Besides, the widget tree is readability and clean. This could be flexible when creating a widget class or a function that returns a widget.

    // Bad
    Scaffold(
      body: Column(
        children: [
          // The Header
          Container(
            child: Stack(
              children: [
                Column(
                  chilren: [
                    Row(
                      children: [
                        Column(
                          children: [
                            Container(
                              // A child
                            ),
                          ],
                        ),
                      ],
                    ),
                    Container(
                      // A child
                    ),
                  ],
                ),
              ],
            ),
          ),
          // The Body
          Expanded(
            child: Row(
              children: [
                Column(
                  children: [
                    Row(
                      children: [
                        ListTile(
                          leading: Column(
                            children: [
                              Container(
                                // A child
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                    Container(
                      // A child
                    ),
                  ],
                ),
              ],
            ),
          ),
          // The Footer
          Expanded(
            child: Column(
              children: [
                Row(
                  children: [
                    Container(
                      // A child
                    ),
                    Container(
                      // A child
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
    
    // Good
    Scaffold(
      body: Column(
        children: [
          _buildHeaderWidget(),
          BodyWidget(),
          FooterWidget(),
        ],
      ),
    );
    
    Widget _buildHeaderWidget() {
      return Container(
        // The Header
      );
    }
    
    class BodyWidget extends StatelessWidget { ... }
    
    class FooterWidget extends StatelessWidget { ... }
    

Naming

The team uses and respects the standardized naming conventions from Effective Dart style. The below guideline is the only slight variation we apply.

  • Capitalize acronyms and abbreviations no matter the length of letters.

    // Bad
    class HTTPRequest {}
    class DBIOPort {}
    class TVVcr {}
    
    // Good
    class HttpRequest {}
    class DbIoPort {}
    class TvVcr {}
    

Localization

  • Our team uses l10n as a main localization.
  • The name of the strings inside .arb files should follow the pattern {feature}{optionalWidgetName}{stringType} and use camelCase format. For example:
    • loginTitle: As we can see, login is a feature, and title is a stringType. We don’t need to specify widgetName since there is only one title for login.
    • loginPhoneNumberTitle: This string looks similar to the above string, but this string is for the PhoneNumber field. In this case, we specify PhoneNumber as a widgetName to distinguish between other titles.
    • errorInvalidPassword: In this case, error is a special type of feature, and InvalidPassword is a stringType.
  • More feature types can be named according to the application features list.