Flutter 🐦

Hero image for Flutter 🐦

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 addition, this will be needed:

  • Xcode for iOS code inspection and editing.

Version Management

Due to the diversity of projects the team works on, each application usually requires different versions of Flutter. Version management allows developers to install and switch between different versions effortlessly.

  • fvm is the recommended version management 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 instance:

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

{project_name}
β”œβ”€β”€ android/
β”œβ”€β”€ ios/
β”œβ”€β”€ web/
β”œβ”€β”€ assets/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ resources/
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”‚   β”œβ”€β”€ home/
β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   └── widgets/
β”‚   β”œβ”€β”€ di/
β”‚   β”‚   β”œβ”€β”€ interceptors/
β”‚   β”‚   β”œβ”€β”€ authenticators/
β”‚   β”‚   β”œβ”€β”€ modules/
β”‚   β”‚   β”œβ”€β”€ providers/
β”‚   β”‚   └── di.dart
β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ local/
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”œβ”€β”€ storages/
β”‚   β”‚   β”‚   └── preferences/
β”‚   β”‚   β”œβ”€β”€ remote/
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”‚   └── models/
β”‚   β”‚   β”‚       β”œβ”€β”€ requests/
β”‚   β”‚   β”‚       └── responses/
β”‚   β”‚   └── repositories/
β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”œβ”€β”€ exceptions/
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── usecases/
β”‚   β”œβ”€β”€ l10n/
β”‚   β”œβ”€β”€ utils (helpers)/
β”‚   └── main.dart
β”œβ”€β”€ integration_test/
β”œβ”€β”€ test/
β”‚   β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ data/
β”‚   └── domain/
└── pubspec.yaml
  • android/, ios/ (also web/, macos/, or windows/): contains all the platform-specific configurations corresponding to Android, iOS, web, or desktop compilation.
  • assets: static assets (images, videos, fonts…) included for the application to use at the compile time.
  • lib: the core logic and presentation for the whole Flutter application. Following Clean Architecture with MVVM, the lib folder includes 3 main layers: app (presentation), data, and domain.
  • test: contains all the unit test suites for each layer.
  • integration_test: contains all the integration test suites.
  • pubspec.yaml: specifies dependencies that the project requires, such as particular packages (and their versions), fonts, or assets files (images, video…).

Exclusion of Generated Files

The team follows β€œWhat not to commit” documentation to not include most of the files that the IDE, code editor, pub tool, and other tools generate. This also includes these various generated file extensions:

*.g.dart
*.gen.dart
*.config.dart
*.freezed.dart
*.mocks.dart
etc...

Excluding them benefits the team to:

  • Avoid wrong statistics from LinearB with substantial code changes from gen code.
  • Have fewer conflicts during the Pull Request merging process.
  • Ease the code review process.

Hence, a code generator is necessary for local development or CI:

$ flutter packages pub run build_runner build --delete-conflicting-outputs

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.

      void createAccount(String firstName, String lastName, String email, String phoneNumber) {
        // Your code
      }
      
      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.

    void setReminder({String? label, DateTime? datetime, bool? enableSnooze}) {
      // Your code
    }
    
    void setReminder({
      String? label,
      DateTime? datetime,
      bool? enableSnooze,
    }) {
      // Your code
    }
    
  • Keywords:
    • required: should be at the same line as the parameters.

      void disableButton({
        required
        Widget button,
        required
        Color color,
      })
      
      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 readable and clean. This could be flexible when creating a widget class or a function that returns a widget.

    Scaffold(
      body: Column(
        children: [
          // The Header
          Container(
            child: Stack(
              children: [
                Column(
                  children: [
                    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
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
    
    Scaffold(
      body: Column(
        children: [
          _buildHeaderWidget(),
          BodyWidget(),
          FooterWidget(),
        ],
      ),
    );
    
    Widget _buildHeaderWidget() {
      return Container(
        // The Header
      );
    }
    
    class BodyWidget extends StatelessWidget { ... }
    
    class FooterWidget extends StatelessWidget { ... }
    

Naming

The standardized naming conventions from Effective Dart style are used and respected by the team, with a minor exception as outlined in the following guideline.

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

    class HTTPRequest {}
    class DBIOPort {}
    class TVVcr {}
    
    class HttpRequest {}
    class DbIoPort {}
    class TvVcr {}
    

Assets & Resources

  • All resources must follow the basic resource naming principle:

    Basic principle

    • <WHAT> indicates what the resource actually represents - optionally, mostly for images.
    • <WHERE> describes where it logically belongs in the app - optionally, common resources could omit this part; all others use the custom part of the screen/feature they are in.
    • <DESCRIPTION> differentiates multiple elements in one screen/feature.
    • <SIZE> is either a precise size or size bucket - optionally used for images and dimensions.
  • Resources in the assets folder must follow the snake_case format in their names.

  • Resources in the lib folder must be defined as constants and follow Dart’s naming conventions for constants.

Images

  • Images must be placed in the assets folder.
  • Images naming must follow the basic resource naming principle above. The <WHAT> part for images is the type such as:

    • Icon images start with an ic_ prefix.
    • Background images start with a bg_ prefix.
    • The rest starts with an img_ prefix.
    close.svg
    selected_home_tab.svg
    splashBackground.png
    empty_image.png
    
    ic_close.svg
    ic_tab_home_selected.svg
    bg_splash.png
    img_empty.png
    

Colors

  • Colors must be defined in the lib folder.
  • Colors naming must follow the basic resource naming principle above.

    static const Color Blue700 = Color(0xFF004370);
    
    static const Color button_main = Blue700;
    static const Color contentBackgroundColor = Colors.white;
    
    static const Color blue700 = Color(0xFF004370);
    
    static const Color buttonPrimaryBackground = blue700;
    static const Color contentBackground = Colors.white;
    

Dimensions

  • Dimensions must be defined in the lib folder.
  • Dimensions naming must follow the basic resource naming principle above.

    static const double spacing12 = 12;
    static const double spacingXX = 16;
    static const double spacingXXX = 24;
    
    static const double date_picker_height_200 = 200;
    
    static const double spacingSmall = 12;
    static const double spacingMedium = 16;
    static const double spacingLarge = 24;
    
    static const double datePickerHeight = 200;
    

Localization

  • The 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: login is a feature, and title is a stringType. There’s only one title for login, so there’s no need to specify widgetName.
    • loginPhoneNumberTitle: This string looks similar to the above string, but this string is for the PhoneNumber field. To differentiate from other titles, PhoneNumber is specified as a widgetName.
    • 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.

Package Id

  • Package id is a unique id that identifies the application in the App Store (iOS) or Play Store (Android).
  • Each platform has its terminology and convention for defining package id. While application id is used for an Android application, iOS applications call it bundle identifier.
    • A valid application id allows alphanumeric characters, periods (.), and underscores (_). Read more about Application Id.
    • A valid bundle identifier allows alphanumeric characters, periods (.), and hyphens (-). Read more about CFBundleIdentifier.