Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jubaerhossain/flutter-best-practices-and-tips


https://github.com/jubaerhossain/flutter-best-practices-and-tips

Last synced: 5 days ago
JSON representation

Awesome Lists containing this project

README

        

## Flutter Best Practice Guidelines

**Placeholder Widgets**

Use SizedBox instead of Container as Placeholder widgets.

```
// Good
return _loaded ? SizedBox() : YourWidget();

// Better
return _loaded ? SizedBox.shrink() : YourWidget();
```

**Define Theme**

Define the Theme of the app as well as a first priority to avoid the headache of changing the theme in future updates. Setting up Theme is surely confusing but one time task. Ask your designer to share all the Theme related data like, Colors, font sizes and weightage.

```
class AppTheme {
AppTheme();

static AppTheme? _current;

// ignore: prefer_constructors_over_static_methods
static AppTheme get current {
_current ??= AppTheme();
return _current!;
}

static ThemeData? lightTheme = ThemeData(
scaffoldBackgroundColor: AppColors.screenBackground,
primaryColor: AppColors.blue,
colorScheme: const ColorScheme.light(secondary: Colors.white),
cardColor: Colors.white,
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.blue,
),
);
}

MaterialApp(
title: appName,
theme: AppTheme.current.lightTheme,
home: const MyHomePage(
title: appName,
),
);
```

**Using `if` instead of Ternary Operator**

Use dart’s built in `if` statement in the layout.

```
// Good
Row(
children: [
Text("Hello Flutter"),
if (Platform.isIOS) Text("iPhone"),
]
);

// Better
Row(
children: [
Text("Hello Flutter"),
if (Platform.isIOS) ...[
Text("iPhone")
Text("MacBook")
],
]
);
```

**Use const widgets whenever possible**

Here the TitleWidget doesn’t change on every build, so its better to make it a const widget.

Advantage: Faster build because TitleWidget will be skipped in the build since it wont change during the process.

```
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: const [
TitleWidget(),
],
),
);
}
}

class TitleWidget extends StatelessWidget {
const TitleWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(10),
child: Text('Home'),
);
}
}
```

**Naming Conventions**

Classes, enums, typedefs, and extensions name should in UpperCamelCase.

```
class MyWidget { ... }
enum Choice { .. }
typedef Predicate = bool Function(T value);
extension ItemList on List { ... }
```

Libraries, packages, directories, and source files name should be in snake_case(lowercase_with_underscores).

```
library firebase_dynamic_links;
import 'my_lib/my_lib.dart';
```

Variables, constants, parameters, and named parameters should be in lowerCamelCase.

```
var item;
const cost = 3.14;
final urlScheme = RegExp('^([a-z]+):');
void sum(int price) {
// ...
}
```

Here is a nice presentation of the above Flutter best practices with examples:

**Avoid Functional Widgets**

**Why:** Functional widgets can make it difficult to inspect your widget tree and can be less performant than class widgets.

**Example:**

```
// Functional widget
Widget functionWidget({Widget child}) {
return Container(child: child);
}

// Class widget
class ClassWidget extends StatelessWidget {
final Widget child;

const ClassWidget({Key? key, required this.child}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(child: child);
}
}
```

**Usage:**

```
// Functional widget
functionWidget(
child: functionWidget(),
);

// Class widget
ClassWidget(
child: ClassWidget(),
);
```

**Widget tree:**

```
// Functional widget
Container
Container

// Class widget
ClassWidget
Container
ClassWidget
Container
```

As you can see, the widget tree for the functional widget is less nested than the widget tree for the class widget. This is because the functional widget is simply creating a new container to wrap the child widget. The class widget, on the other hand, is creating a new widget to wrap the child widget.

The extra nesting in the widget tree can make it more difficult to inspect and debug your code. It can also lead to performance issues, as the Flutter engine has to render each widget in the tree.

**Recommendation:** Use class widgets instead of functional widgets whenever possible.

**Specify Types for variables**

**Why:** Specifying types for variables can make your code more readable, maintainable, and less prone to errors.

**Example:**

```
// Without type specification
var count = 10;
final person = Person();
const timeOut = 6000;

// With type specification
final int count = 10;
final Person person = Person();
const int timeOut = 6000;
```

**Recommendation:** Specify types for variables whenever possible.

**Use `is` instead of `as`**

**Why:** Using the `is` operator to check if a variable is of a certain type is more robust than using the `as` operator to cast a variable to a certain type.

**Example:**

```
// Using `as`
(item as Person).name = 'John Doe';

// Using `is`
if (item is Person) {
item.name = 'John Doe';
}
```

**Recommendation:** Use the `is` operator to check if a variable is of a certain type before casting it to that type.

**Use `ItemExtent` in `ListView` if you have longer lists**

**Why:** Specifying the `itemExtent` property of a `ListView` can improve its performance if it has a large number of items.

**Example:**

```
// Without `itemExtent`
ListView(
children: List.generate(10000, (index) => Text('Index: $index')),
)

// With `itemExtent`
ListView(
itemExtent: 600,
children: List.generate(10000, (index) => Text('Index: $index')),
)
```

**Recommendation:** Specify the `itemExtent` property of a `ListView` if it has a large number of items.

**Use `??` and `.?.` operators**

**Why:** The `??` and `.?.` operators can be used to make your code more concise and less prone to errors.

**Example:**

```
// Without `??` and `.?.`
var done = null;

bool isValid = done != null ? true : false;

String name = person != null ? person.name : null;

// With `??` and `.?.`
bool isValid = done ?? false;

String name = person?.name;
```

**Recommendation:** Use the `??` and `.?.` operators to make your code more concise and less prone to errors.
**Use Raw Strings**

Raw strings allow you to write strings in your code without having to escape certain characters, such as backslashes and dollar signs. This can make your code more readable and easier to maintain.

To use a raw string, simply prefix the string with the `r` character. For example:

```
var s = r'This is a raw string. I don\'t have to escape backslashes \ or dollar signs $.';
```

**Avoid `print()` calls**

The `print()` function is used to print output to the console. However, it is generally not recommended to use `print()` in production code. This is because `print()` can be slow and can also pollute the console with output that is not relevant to the user.

Instead of using `print()`, you should use a logging library such as `debugPrint()` or `log()`. These libraries provide more control over how and when output is logged.

To use `debugPrint()`, simply pass the string you want to print to the `debugPrint()` function. For example:

```
debugPrint('This is a debug message.');
```

To use `log()`, you need to first import the `dart:developer` library. Then, you can call the `log()` function with the string you want to log. For example:

```
import 'dart:developer';

log('This is a log message.');
```

**Use `print` Statements only in Debug Mode**

If you do need to use `print()` statements in your code, it is a good practice to only use them in debug mode. This will help to prevent them from polluting the console in production code.

To check if your code is running in debug mode, you can use the `kDebugMode` constant. For example:

```
if (kDebugMode) {
print('This is a debug message.');
}
```

**Use leading underscore (‘_’) for private variables**

Private variables are variables that are only accessible to the class in which they are defined. To make a variable private, simply prefix the variable name with an underscore (`_`).

For example:

```
class MyClass {
final int _myPrivateVariable = 10;

int get myPublicVariable {
return _myPrivateVariable;
}
}
```

In this example, the `_myPrivateVariable` variable is private. It can only be accessed by methods within the `MyClass` class. The `myPublicVariable` getter method can be used to access the `_myPrivateVariable` variable from outside the `MyClass` class.

**Use `final` / `const` keyword for unmodified variables**

The `final` keyword is used to declare a variable that can only be assigned a value once. The `const` keyword is used to declare a constant variable, which is a variable whose value cannot be changed.

You should use the `final` or `const` keyword for any variable that will not be modified after it is assigned a value. This can make your code more readable and easier to maintain.

For example:

```
final String myName = 'John Doe';
const int myAge = 30;
```

In this example, the `myName` variable is final and the `myAge` variable is constant. The value of neither variable can be changed after it is assigned a value.

Here is a more detailed explanation of the Flutter best practices you mentioned:

**Use `final`/`const` keyword for unmodified variables**

The `final` keyword is used to declare a variable that can only be assigned a value once. The `const` keyword is used to declare a constant variable, which is a variable whose value cannot be changed.

You should use the `final` or `const` keyword for any variable that will not be modified after it is assigned a value. This can make your code more readable and easier to maintain.

For example:

```
final String myName = 'John Doe';
const int myAge = 30;
```

In this example, the `myName` variable is final and the `myAge` variable is constant. The value of neither variable can be changed after it is assigned a value.

**Don't use `+` for concatenating strings, use string interpolation**

String concatenation using the `+` operator can be slow and inefficient, especially when concatenating long strings. Instead of using `+`, you should use string interpolation.

To use string interpolation, simply enclose the variables you want to concatenate in double curly braces (`{{}}`). For example:

```
final String firstName = 'John';
final String text = '$firstName Doe';
```

The `text` variable will contain the string `John Doe`.

**Don't create a lambda when a tear-off will do**

A tear-off is a way of calling a method on an object without having to explicitly create the object. Tear-offs are more efficient than lambdas, so you should use them whenever possible.

For example, the following code uses a lambda to call the `print()` method on each element of a list:

```
List names = [];

// Don't
names.forEach((name) {
print(name);
});
```

This can be rewritten using a tear-off as follows:

```
List names = [];

// Do
names.forEach(print);
```

The `forEach()` method expects a function that takes a single argument, which is the element of the list. In this case, we are passing the `print()` method as a tear-off.

**Using `async`/`await` more readable way**

The `async`/`await` keywords can make your code more readable and easier to maintain by allowing you to write asynchronous code in a sequential style.

For example, the following code uses callbacks to get the number of users and then handle any errors:

```
Future getUsersCount() {
return getUsers().then((users) {
return users?.length ?? 0;
}).catchError((e) {
log.error(e);
return 0;
});
}
```

This can be rewritten using `async`/`await` as follows:

```
Future getUsersCount() async {
try {
var users = await getActiveUser();
return users?.length ?? 0;
} catch (e) {
log.error(e);
return 0;
}
}
```

This version of the code is more readable and easier to maintain because it is written in a sequential style.

**Use `ListView.builder` for a building list with same views.**

The `ListView.builder()` constructor creates a `ListView` that builds its rows on demand. This can be more efficient than using the `ListView()` constructor, especially for long lists.

The `ListView.builder()` constructor takes three arguments:

* The number of items in the list.
* A builder function that takes an index and returns a widget.
* An optional itemExtent, which is the height of each item in the list.

For example, the following code creates a `ListView` with 100 items:

```
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Text('Item $index');
},
)
```

The `itemBuilder` function is called for each item in the list. It takes an index and returns a widget, which is displayed in the `ListView`.

**Don't explicitly initialize variables to null**

In Dart, variables are automatically initialized to null when their value is not specified, so explicitly initializing a variable to null is unnecessary and redundant.

**Example:**

```dart
// Don't
int _val = null;

// Do
int? _val;
```

The `int?` type indicates that the variable `_val` may be null. This is more descriptive and can help to prevent bugs.

**Use expression function bodies**

For functions that contain just one expression, you can use an expression function. The `=>` (arrow) notation is used for expression function bodies.

**Example:**

```dart
// Don't
get name {
return '$firstName $lastName';
}
Widget getLoading() {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.blue),
);
}

// Do
get name => '$firstName $lastName';
Widget getLoading() => CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.blue),
);
```

Expression function bodies are more concise and easier to read.

**Use cascading operator**

The cascading operator (`..`) allows you to perform a sequence of operations on the same object without having to repeat the object name each time.

**Example:**

```dart
// Don't
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
path.close();

// Do
var path = Path()
..lineTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, 0)
..close();
```

The cascading operator makes the code more concise and easier to read.

**Use spread collections**

Spread collection syntax allows you to easily add existing items to a new collection.

**Example:**

```dart
// Don't
var y = [4,5,6];
var x = [1,2];
x.addAll(y);

// Do
var y = [4,5,6];
var x = [1,2,...y]; // 1,2,4,5,6
```

Spread collection syntax makes the code more concise and easier to read.

**Use literal to initialize growable collections**

It is recommended to use literal syntax to initialize growable collections, such as lists and maps. This makes the code more concise and easier to read.

**Example:**

```dart
// Good
var points = [];
var addresses = {};

// Bad
var points = List();
var addresses = Map();
```

**Use Colors in a separate file**

It is a good practice to have all of the colors in your application in a single file. This makes it easier to manage and update your colors.

**Example:**

```dart
class AppColor {
static const Color red = Color(0xFFFF0000);
static const Color green = Color(0xFF4CAF50);
static const Color errorRed = Color(0xFFFF6E6E);
}
```

**Use Dart Code Metrics**

Dart Code Metrics is a static code analysis tool that can help you to improve the quality of your Flutter code. It provides a number of metrics that you can use to identify and address potential problems.

To use Dart Code Metrics, simply run the following command in your terminal:

```
dart analyze
```

This will generate a report that lists any potential problems in your code.

**Flutter Security Best Practices**

Security is an important consideration for any mobile app. Flutter provides a number of security features, but it is important to use them correctly.

Here are some Flutter security best practices:

* **Use code obfuscation.** Code obfuscation makes it more difficult for attackers to reverse engineer your app and steal your code or data.
* **Prevent background snapshots.** Background snapshots can reveal sensitive information about your app's state. To prevent background snapshots, use the `secure_application` Flutter package.
* **Use the underscore (_).** The underscore (_) can be used to indicate that a variable is not used. This can help to prevent bugs and make your code more readable.

**Example:**

```dart
someFuture.then((_) => someFunc());
```

In this example, the underscore indicates that the variable `DATA_TYPE VARIABLE` is not used. This makes the code more readable and can help to prevent bugs.

**Proper naming for magic numbers**

Magic numbers are literal values that appear in your code without any explanation. This can make your code difficult to read and understand, and it can also make it difficult to maintain.

**Example:**

```dart
// Don't
SvgPicture.asset(
Images.frameWhite,
height: 13.0,
width: 13.0,
);

// Do
final _frameIconSize = 13.0;
SvgPicture.asset(
Images.frameWhite,
height: _frameIconSize,
width: _frameIconSize,
);
```

In the first example, the magic number `13.0` appears twice, without any explanation. In the second example, we have defined a named constant `_frameIconSize` to represent the magic number. This makes the code more readable and maintainable.

**Understanding the concept of constraints in Flutter**

Constraints in Flutter are objects that represent the size and position of a widget. Constraints are passed down from parent widgets to child widgets.

The thumb rule of a Flutter layout is that constraints go down, sizes go up, and the parent sets the position. This means that parent widgets determine the size and position of their child widgets.

**Widgets choice matters**

The choice of widgets can have a big impact on the performance of your Flutter app. For example, using a `ListView` instead of a `Column` for a long list of items can improve performance.

**Use packages only when necessary**

Packages can be a great way to add new features and functionality to your Flutter app. However, it is important to use packages only when necessary, as they can add overhead to your app and reduce performance.

**Linting**

Linting is a process of analyzing your code for potential errors and style violations. Flutter comes with a built-in linter that can help you to improve the quality of your code.

To enable linting, add the following to your `analysis_options.yml` file:

```
linter:
rules:
- avoid_print
```

This will enable the `avoid_print` lint rule, which will warn you if you use the `print()` function in your code.

**Do something after the build completes can improve performance**

If you need to do something after the build completes, you can use the `WidgetsBinding.instance.addPostFrameCallback()` method. This method will call the given callback after the next frame is rendered.

**Example:**

```dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State {
@override
void initState() {
super.initState();

WidgetsBinding.instance.addPostFrameCallback((_) {
// Do something after the build completes.
});
}

@override
Widget build(BuildContext context) {
return Container();
}
}
```

**Use SafeArea**

The `SafeArea` widget insets its child by removing padding required to OS controls like the status bar and bottom navigation buttons. This can help to ensure that your content is always visible to the user.

**Example:**

```dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Container(),
),
);
}
}
```

**Use keys to improve Flutter performance**

Keys can be used to improve the performance of Flutter apps by helping the framework to identify and update widgets more efficiently.

**Example:**

```dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State {
final GlobalKey _key = GlobalKey();

@override
Widget build(BuildContext context) {
return Container(
key: _key,
);
}
}
```

In this example, we have used the `GlobalKey` class to create a key for the `Container` widget. This key can be used to identify the `Container` widget in the widget tree.

**Analyze & Decrease Application Size**

When developing a Flutter app, it is important to analyze and decrease the application size. This can be done by using the Flutter development tool