Best Practices To Ease Flutter App Development In 2024

Posted By

naxtre

Published Date

28-12-2023
Best Practices to Ease Flutter App Development in 2024

Best Practices To Ease Flutter App Development In 2024

One of Google's most well-liked cross-platform mobile frameworks is Flutter. Because the framework has been extensively embraced by developers worldwide, Flutter has been updated often; the most recent version is Flutter 3. We're going to discuss recommended practices for designing Flutter apps today; reading this blog will make the process of creating an app using Flutter easier.

Flutter Best Practice for App Development

The best practices for Flutter developers to increase productivity, readability, maintainability, and code quality are covered here. Now let's get moving:

1.  Make the build function pure

The build method is developed in such a way that it has to be pure/without any unwanted stuff. This is because there are certain external factors that can trigger a new widget build, below are some examples:

  • Route pop/push
  • Screen resize, usually because of keyboard appearance or orientation change
  • The parent widget recreated its child
  • An Inherited Widget the widget depends on (Class. of(context) pattern) change

Avoid:

@override

Widget build(BuildContext context) {

  return FutureBuilder(

    future: httpCall(),

    builder: (context, snapshot) {

      // create some layout here

    },

  );

}

Should be like this:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = repository.httpCall();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

2. Understanding the concept of constraints in Flutter

Every Flutter app developer should be aware of the following layout maxims: parent sets position, sizes go up, and restrictions go down. Let's learn more about the same thing:

A widget inherits its parent's limitations. A minimum and maximum height, as well as a minimum and maximum breadth, are the four doubles that make up a restriction.

The widget then looks through its own list of kids. The widget asks each child what size it wishes to be after instructing them one by one about their limits, which might vary from child to child.

The widget then arranges its children one after the other, horizontally along the x axis and vertically along the y axis. Next, the widget (within the original limits, of course) tells its parent about its own size.

In Flutter, all widgets give themselves on the basis of their parent or their box constraints. But this has some limitations attached.

For example, suppose you would want to choose the size of a child widget that is inside a parent widget. There is no size that the widget can have on its own. The widget's size must not exceed the limitations that its parent has set.

3. Smart use of operators to reduce the number of lines for execution

Use Cascades Operator

If we are supposed to perform a sequence of operations on the same object then we should opt for Cascades(..) operator.

//Do

var path = Path()

..lineTo(0, size.height)

..lineTo(size.width, size.height)

..lineTo(size.width, 0)

..close();

 

//Do not

var path = Path();

path.lineTo(0, size.height);

path.lineTo(size.width, size.height);

path.lineTo(size.width, 0);

path.close();

Use spread collections

You can use spread collections when existing items are already stored in another collection, spread collection syntax leads to simpler and easier code.

//Do

var y = [4,5,6];

var x = [1,2,...y];

 

//Do not

var y = [4,5,6];

var x = [1,2];

x.addAll(y);

Use Null safe (??) and Null aware (?.) operators

Always go for ?? (if null) and ?. (null aware) operators instead of null checks in conditional expressions.

//Do     

v = a ?? b;

//Do not

v = a == null ? b : a;

 

//Do

v = a?.b;

//Do not

v = a == null ? null : a.b

Avoid using “as” operator instead of that, use “is” operator

Generally, the as cast operator throws an exception if the cast is not possible. To prevent an exception being thrown, one can use `is`. 

//Do

if (item is Animal)

item.name = 'Lion';

 

//Do not

(item as Animal).name = 'Lion';


4. Use streams only when needed

Even while streams are a rather strong resource, using them puts a lot of duty on us to use them effectively.

Inadequate Stream implementation might result in higher CPU and memory use. Not only that, but memory leaks will result if you neglect to shut the streams.

Therefore, in these situations, you may use something more that uses less memory, such ChangeNotifier for reactive UI, instead of Streams. We may utilize the Bloc library, which focuses more on resource efficiency and provides a straightforward interface for creating a reactive user interface, for more sophisticated functions.

As long as streams aren't being utilized, they will be cleaned adequately. The problem here is that eliminating the variable alone won't guarantee it isn't used. It is still able to operate in the background.

Calling Sink.close() is necessary to ensure that the related StreamController is stopped and that the GC can release resources later.

You must use StatefulWidget.dispose of technique in order to do that.

abstract class MyBloc {

  Sink foo;

  Sink bar;

}

 

class MyWiget extends StatefulWidget {

  @override

  _MyWigetState createState() => _MyWigetState();

}

 

class _MyWigetState extends State<MyWiget> {

  MyBloc bloc;

 

  @override

  void dispose() {

    bloc.bar.close();

    bloc.foo.close();

    super.dispose();

  }

 

  @override

  Widget build(BuildContext context) {

    // ...

  }

}

5.  Write tests for critical functionality

Because there are always going to be unforeseen circumstances when doing manual testing, having an automated set of tests may help you save a significant amount of time and work. Since Flutter primarily targets various platforms, it would take a lot of time and repetitive effort to test every feature after every change.

The ideal choice for testing is always to have 100% code coverage, but given the constraints of time and money, this may not always be feasible. However, it's still imperative to have tests covering the app's fundamental functions.

When starting off, unit and widget tests are the best choices because they are less laborious than integration tests.

6. Use raw string

A raw string can be used to not come across escaping only backslashes and dollars.
//Do

var s = r'This is demo string  and $';

 

//Do not

var s = 'This is demo string \ and $';

7. Use relative imports instead of absolute imports

It is easy to cause confusion when combining relative and absolute imports when the same class is imported using two separate methods. We should utilize a relative path in the lib/ subdirectory to get around this situation.

//Do

import '../../themes/style.dart';

 

//Do not

import 'package:myapp/themes/style.dart';

8. Using SizedBox instead of Container in Flutter

There are several situations in which using a placeholder will be necessary. Below is the perfect illustration: 

return _isNotLoaded ? Container() : YourAppropriateWidget();

You will use the Container widget a lot in Flutter; it's a terrific widget. Container() is not a const constructor; instead, it expands to suit the parameters provided by the parent. 

The SizedBox, on the other hand, creates a fixed-size box and is a const constructor. You can set the width and height parameters to null to indicate that the box's size shouldn't be limited in their respective dimensions. 

Thus, SizedBox should be used instead of a container when we have to implement the placeholder. 

return _isNotLoaded ? SizedBox() : YourAppropriateWidget();

9. Use log instead print

print() and debugPrint() both are always applied for logging in to the console. If you are using print() and you get output which is too much at once, then Android discards some log lines at times.

To not face this again, use debugPrint(). If your log data has more than enough data then use dart: developer log(). This enables you to add a bit more granularity and information in the logging output.

//Do

log('data: $data');

 

//Do not

print('data: $data');

Use ternary operator for single-line cases.

String alert = isReturningCustomer ? 'Welcome back to our site!' : 'Welcome, please sign up.'; 

Use if condition instead of ternary operator for the case like below.

Widget getText(BuildContext context) {

    return Row(

        children:

        [

            Text("Hello"),

            if (Platform.isAndroid) Text("Android") (here if you use ternary then that is wrong)

        ]

    );

}

Always try to use const widgets. The widget will not change when setState call we should define it as constant. It will impede the widget from being rebuilt so it revamps performance.

//Do

const SizedBox(height: Dimens.space_normal)

 

//Do not

SizedBox(height: Dimens.space_normal)

10. Don’t explicitly initialize variables null

In Dart, the variable is intuitively initialized to null when its value is not specified, so adding null is redundant and unrequired. 

//Do

int _item;

 

//Do not

int _item = null;

Always highlight the type of member when its value type is known. Do not use var when it is not required. As var is a dynamic type takes more space and time to resolve.

//Do

int item = 10;

final Car bar = Car();

String name = 'john';

const int timeOut = 20;

 

//Do not

var item = 10;

final car = Car();

const timeOut = 2000;

11. Use the const keyword whenever possible

Garbage collector workload can be reduced by using a const constructor for widgets. At first, this may seem like a little performance, but as the app is large enough or there is a view that is frequently rebuilt, it adds up and becomes noticeable.

Additionally, const declarations support hot reloads better. Additionally, we ought to disregard the superfluous const keyword. Examine the code that follows:

const Container(

  width: 100,

  child: const Text('Hello World')

);

We don’t require to use const for the Text widget since const is already applied to the parent widget. 

Dart offers following Linter rules for const: 

prefer_const_constructors

prefer_const_declarations

prefer_const_literals_to_create_immutables

Unnecessary_const

12. Some cosmetic points to keep in mind

  • Make sure to always enclose your root widgets in a safe place. 
  • Multiple variables can be declared using the shortcut (int mark = 10, total = 20, amount = 30;). 
  • When possible, be careful to utilize final/const class variables. 
  • Steer clear of unnecessary commented codes. 
  • Whenever feasible, create private methods and variables. 
  • Create distinct classes for variables like time, constant strings, colors, text styles, and dimensions. 
  • Create API keys and API constants. 
  • Avoid using functions and global variables. They must have some connection to the class. 
  • Examine the dart analysis and heed its advice. 
  • Verify the underlining that offers optimization or typo suggestions. 
  • If the value is not utilized within the code block, use _ (underscore).

//Do

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

 

//Do not

someFuture.then((DATA_TYPE VARIABLE) => someFunc()); 

Magical numbers always have proper naming for human readability. 

//Do

final _frameIconSize = 13.0;

 SvgPicture.asset(

 Images.frameWhite,

 height: _frameIconSize,

 width: _frameIconSize,

 );

 

//Do not

SvgPicture.asset(

Images.frameWhite,

height: 13.0,

width: 13.0,

);

Alright, here we are

Thus, this article focused on Flutter development best practices that comparatively lighten the workload of all Flutter developers.

Speak with us if you need help creating a Flutter app or if you want to work with professional Flutter developers on your project. Our skilled group of Flutter developers is here to help you with your project.

Let's Talk
About Your Idea!