When you hear about Flutter you know it's the best cross-platform framework out there for mobile app development because it is from Google and they are constantly doing a lot to improve it day by day. But don't be fooled to think a framework launched 5 years ago doesn't bring along with struggles seen elsewhere despite the constant improvements it has seen.
One big elephant in the room that I personally as a developer struggled with and still do is that of App performance. It doesn't matter how beautiful your app is and how intuitive the design is, if your UX sucks then your app is not good and it will not last long on the user's phone. If you have a web version of the app, the users will route for that.
Last month I got to attend DroidConKe 2022 where one of the sessions that caught my attention was that of Sam Baraka who spoke of the most crucial topics of all in flutter. Such a subject is rare to hear about or be interested in when starting on Flutter.
My article will major on what he talked about as well as what I have learned lately, on the subject. So here we go!
1. Refactor your code into Widgets Instead of Methods
Extracting widgets to a method is considered a Flutter anti-pattern, because when Flutter rebuilds the widget tree, it calls the function all the time, making more processor time for the operations. Simply avoid returning widgets!
Take a look at the sample code below.
class MyWidget extends StatelessWidget {
const MyWidget();
Widget bodyContainer() {
return Container(
child: Column(
children: [
Text('Mbele naona success'),
],
),
);
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Mapema ndio Best'),
bodyContainer(),
],
);
}
}
a. Refactor into Widgets
The solution for this one is relatively simple, although it results in a couple of extra lines of code. Instead of splitting build methods into smaller methods, we split them into widgets — StatelessWidgets, that is.
When we refactor the previous example, we’ll end up with this:
class MyWidget extends StatelessWidget {
const MyWidget();
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Mapema ndio Best'),
const BodyContainer(),
],
);
}
}
class BodyContainer extends StatelessWidget {
const BodyContainer();
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text('Mbele naona success'),
],
),
);
}
}
b. Refactor into Methods
Text buildMbeleNaonaSuccess() => Text('Mbele naona success');
Now refactoring into a method might seem tempting here but when buildHello is too big it might rebuild even when nothing inside buildHello was changed.
But when it comes to refactoring into widgets, we get all the benefits of the widget lifecycle so it will rebuild only when something in the widget changes. So this prevents unnecessary rebuilds and thus improves performance. This also offers all the optimizations that Flutter offers for the widget class.
2. Use const keyword whenever possible
Using const constructor for widgets can reduce the work required for garbage collectors. This may initially seem like a small performance improvement but it adds up and makes a difference when the app is big enough or there is a view that gets frequently rebuilt.
Const declarations are also more hot-reload friendly. Also, we should avoid the unnecessary const keyword. Consider the following code:
const Container(
width: 100,
child: const Text('Hello World')
);
Here we don't need to use const for the Text widget since const is already applied to parent widget.
Dart offers the following Linter rules for const:
3. Using SizedBox instead of Container
There are multiple use cases where you will require to use a placeholder. Here is the ideal example below:
return isNotLoaded ? Container() : YourAppropriateWidget();
The Container is a great widget that you will be using extensively in Flutter. Container() broadens up to fit the constraints given by the parent and is not a const constructor.
On the contrary, the SizedBox is a const constructor and builds a fixed-size box. The width and height parameters can be null to specify that the size of the box should not be constrained in the corresponding dimension.
Thus, when we have to implement the placeholder, SizedBox should be used rather than using a container.
return isNotLoaded ? SizedBox() : YourAppropriateWidget();
4. Smart use of operators to reduce the number of lines for execution
a) Use Cascades Operator
If we are supposed to perform a sequence of operations on the same object then we should opt for the Cascades(..) operator.
Do not ...
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
path.close();
Instead, do ...
var path = Path()
..lineTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, 0)
..close();
b) 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.
Don't ...
var y = [4,5,6];
var x = [1,2];
x.addAll(y);
Instead, do ...
var y = [4,5,6];
var x = [1,2,...y];
c) Use Null safe (??) and Null aware (?.) operators
Always go for ?? (if null) and ?. (null aware) operators instead of null checks in conditional expressions.
Don't ...
v = a == null ? b : a;
Instead, do ...
v = a ?? b;
Also don't ...
v = a == null ? null : a.b;
Instead, do ...
v = a?.b;
d) 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
.
Don't ...
(item as Animal).name = 'Lion';
Instead, do ...
if (item is Animal)
item.name = 'Lion';
5. Use relative imports instead of absolute imports
When using relative and absolute imports together then It is possible to create confusion when the same class gets imported from two different ways. To avoid this case we should use a relative path in the lib/ folder.
Don't import this way ...
import 'package:myapp/themes/style.dart';
Instead, import this way ...
import '../../themes/style.dart';
6. Use raw string
A raw string can be used to not come across escaping only backslashes and dollars.
Don't ...
var s = 'This is demo string \ and $';
Instead, do ...
var s = r'This is demo string and $';
If you have reached here in this long article, a big thank you see you in Part 2 of this amazing topic.
Don't fail to Follow me here and On
Twitter @JacksiroKe
Linked In Jack Siro
Github @JacksiroKe