DEV Community

Cover image for Love, technology and the new Internet. Created on the basis of Flutter.
Dmitry Sorokin
Dmitry Sorokin

Posted on

Love, technology and the new Internet. Created on the basis of Flutter.

Dubai EXPO

We have always wanted to do something unusual, something that can turn this world upside down, change it for the better. My team and I have created a product based on the foundations of privacy, branding, and user experience! And behind the creation of this product is my personal story of unrequited love. If you are interested in reading about the latter, you can go to Medium.com We recently posted the source code of the project on on GitHub on my behalf with detailed README.md , who are interested, welcome!

The client application for desktop and mobile devices is written in Dart using the Flutter framework. In this article I would like to talk about the framework itself as a whole.

So, best practices for app development with Flutter

1. Keeping the build function clean.

For the build function to work effectively, it is necessary to ensure its maximum "purity", i.e. the absence of unnecessary elements of the program code. This is because there are certain external factors that can trigger a new Widget build, below are some examples:

_- navigation in the application;

  • resizing of the screen, usually due to showing/hiding the keyboard or changing the screen orientation;
  • the parent widget has recreated its child widget;
  • Inherited Widget depends on changing values (Class. of(context) pattern)._

An example of incorrect code looks like this:

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

An example of correct code should look 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
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Understanding the principles of Flutter's constrains concept.

There is one widely accepted rule of thumb regarding Flutter layouts that every Flutter app developer should follow: Constraints decrease, dimensions increase, and the parent determines the position. Let's look at this issue in more detail.

A widget has its own constraints, which are determined by its parent widget. A constraint is a set of four pairs of values: the minimum and maximum width, and the minimum and maximum height.

The widget then goes through its own list of child elements. The widget sets its child elements in turn with constraints (which can be different for each element), and then asks each such element for information about the desired size.

Next, the widget places its child elements one after the other (horizontally on the x-axis and vertically on the y-axis). The widget then passes its own size information to its parent element (within the original constraints).

**

In Flutter, all widgets define themselves based on the constraints of their parent element or the constraints of their box.
** However, there are a number of limitations here.

For example, if you have a child widget inside a parent widget and you want to set its size. A widget cannot have any size on its own. The size of a widget must be within the limits set by its parent element.

3. Rational use of operators to reduce the number of executed lines.

_

Use the Cascades operator
_

If you need to perform a sequence of operations for the same object, then you should choose the 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();
Enter fullscreen mode Exit fullscreen mode

_

Use Spread Collections
_

You can use spread collections when existing elements are already stored in another collection. The spread collection syntax allows for simpler 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);
Enter fullscreen mode Exit fullscreen mode

_

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

Always use the ?? (if null) and ?. (null aware) instead of checking for an undefined value (null).

//Do    
v = a ?? b; 
//Do not
v = a == null ? b : a;

//Do
v = a?.b; 
//Do not
v = a == null ? null : a.b;
Enter fullscreen mode Exit fullscreen mode

_

Avoid using the as operator, use the is operator instead.
_

Generally, the as cast operator throws an exception if the cast is not possible. In such cases, the is operator can be used.

//Do
if (item is Animal)
item.name = 'Lion';

//Do not
(item as Animal).name = 'Lion';
Enter fullscreen mode Exit fullscreen mode

4. Use the Streams feature only when necessary.

Although the Streams feature is quite powerful and efficient, it places a heavy burden on hardware resources.

Incorrect implementation can lead to increased consumption of memory and processor resources. But it's not only that. If you forget to stop the execution of the Streams function, this will leak memory.

Therefore, in such cases, another tool that consumes less memory resources, such as ChangeNotifier, can be used for a reactive user interface. For more advanced features, we can choose the Bloc library, which makes efficient use of hardware resources and offers a set of simple tools for creating a reactive user interface.

If the Streams feature is not used, its data will be effectively deleted. The thing is, if you just remove the variable, it won't be enough to make sure it's not being used. It may continue to run in the background.

You need to call Sink.close() to stop the corresponding StreamController to make sure the memory resources can be freed later with the GC's cleanup function.

To do this, you need to use the StatefulWidget.dispose method:

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) {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Writing tests for checking critical functions.

You can't insure yourself against manual testing errors, but having an automated test suite can save you a significant amount of time and effort. Since the Flutter SDK is designed for a large number of platforms, testing each feature after each change will be time-consuming and requires a lot of repetitive work.

Checking the entire program code as a whole guarantees the best results, but its implementation is not always possible due to limited time and funds. However, it is necessary to have tests at least to verify the basic critical functions of the application.

Tests to test individual units and widgets will be optimal solutions to use from the very beginning, and their execution will not be as difficult as compared to integration tests.

Katya ® 👽

Summing up, I would like to note that over the years of work we have formed a strong and stable community that is growing and developing to this day.
Hundreds of thousands of users daily monitor our activities around the world and constantly offer their help, everyone contributes to the development and improvement of our products! It's great!

Sincerely,
Yours, Dmitry Sorokin

403 Gone,
REChain, Inc
Katya AI, Systems
Katya, Inc
Katya Systems, LLC
REChain Network Solutions

Top comments (0)