Posted By
naxtre
Published Date
28-12-2023
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.
The best practices for Flutter developers to increase productivity, readability, maintainability, and code quality are covered here. Now let's get moving:
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:
Avoid:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
Should be like this:
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.
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';
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) {
// ...
}
}
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.
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 $';
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();
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)
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;
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
//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,
);
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!