Basics of clean code— Flutter project [UI Part]
Learn how to code in Dart in a way that every other developer can understand
I’m writing this article because one of my colleagues asked me to help him to finish the integration of API inside one app, which he claimed to be 95% finished and the remaining work needed two days. At first, we agreed that I would take a look at the source code and let him know if I can help or not. I’m one of the ‘red people’ and my decisions are fast, precise, with not so much thinking and I was pretty sure that I will tell him YES. After 5 hours of looking and planning, what’s the best way to implement it, my answer was NO. The main reason to make this (for me difficult decision) lies in this quote:
It doesn’t require an awful lot of skill to write a program that a computer understands.
The skill is writing programs that human understand.
—
Robert C. Martin
Yes, after hours of looking, I didn’t get the program flow. Widgets, providers, business logic, helper functions were all mixed. The naming of files was terrible; with a mix of underscore separation, case-sensitive and not case-sensitive names. You couldn't find any design patterns, but you could find files with more than 1000 lines of code and you should, of course, use a horizontal scroll, setState()
in every file, for every ten lines called. And the worst thing, there is no single test for an application that has more than 20 screens.
Sure, I could continue the mess and implement APIs praying that I don’t break something, but a real professional wouldn’t do that. So, my final answer was unfortunately NO. Where did it go wrong? What should developers do so that the next devs could continue their work?
Testing, testing, and TESTING
Keywords: #FlutterTests #TDD #Unit #Integration #WidgetTests
The most important thing here is to write tests. This will enable you to be secure that someone else will not break your implementation even if it is badly styled. Of course, writing tests will significantly improve your code implementation because you will need to separate a code into smaller pieces (functions/method) to make it unit testable.
One of the approaches would be to go with test-driven development (TDD) so that every part of the code is covered by a test. You will need to spend more time on your project because of writing tests, but for sure it will give more stability. And most importantly, developers who will come after you will know that they can add new features without breaking existing ones.
You should take testing as part of your code implementation. Are you driving a car without a rooftop when it’s raining?
1000 Lines of Code (LoC)
Keywords: #LoC
Please avoid files with more than 500 lines of code for widget files, of course for non-widget files this should be a much smaller number. I, as a new developer in the project, cannot find one TextField widget in the jungle of lines, because some guy on the other side didn’t use the feature of creating files?
Separate your widgets into files, put them into one directory if you need to group them, and give them appropriate names. All future developers will be thankful for it.
For example, we will refactor the content of this file.
Note: This file has around 160 lines of code.
Because you are reading the section about reducing the LOC, we will do that first. I will extract widgets for the header section, comments list, and text field for adding new comments.
Now each of these files has less than 100 lines of code, has a proper name, and, most importantly, it’s more readable.
File and directories names
Based on the effective dart style, you should name your files lowercase and if you need to separate words, you should use underscore. The reason is here:
Some file systems are not case-sensitive, so many projects require filenames to be all lowercase. Using a separating character allows names to still be readable in that form. Using underscores as the separator ensures that the name is still a valid Dart identifier, which may be helpful if the language later supports symbolic imports.
—
From https://dart.dev/guides/language/effective-dart/style
Code formatting
Keywords: #AutoFormatting #CleanCode
From the example below, you can see that our code looks like banana code:
There are two things that we can do, to resolve the problem:
- Use _build..() functions to achieve a more readable and cleaner code. We will create: _buildAppBar(), _buildBody(), _buildCommentContainer(). As you can see, if you name them correctly you will exactly know where to go to make some changes on this screen.
Note: Please be aware, that because of performance it’s better to create stateless widgets instead of helper functions. You can read more about it inside the comment section of this article.
2. I recommend the usage of the auto-formatting feature, but to make sure this feature works properly you should add commas after every parameter value line.
Tip: Use the auto-format option from your IDE. For flutter related shortcuts you can always read this great article. Pay attention, that you can get more shortcuts from the comments section.
As you can see, if I add a comma after Colors.black
and do auto-formatting it will look more readable. But when to use it, when not? I’m using a comma after every value that I have provided to a particular property. In this way, it will be the same everywhere with exceptions for widgets where I’m providing only one value.
SizedBox(height: 10),
After changes, our comments_screen.dart will look like this:
Method/function calls
Keywords: #Methods #Functions
Because you are reading a clean code article, I will not explain to you when you should use what approach to provide parameters to your functions. Just remember that everything above two unnamed parameters will be hard to read when calling a method or function. Exception is when something is too obvious from the name of the function:
_calculateSumOfTwoNumbers(2, 2);
_divideTwoNumbers(dividend: 2, divisor: 2)
Inside functions or constructors, where you are providing widgets, please use named parameters, but if you have 10 different parameters it’s again not the best idea to provide 10 named parameters. There are two main reasons for that:
- Intellisense can help us, to remember names if we are writing code at bottom of the file
- If you need to pass those parameters to some other class, you will then only provide one parameter that has 10 properties.
Anyhow, methods, and functions are a bigger topic, and they are representing operational things, which I will explain to you how to use clean code principles in some of the next articles.
Tip: Use arrow functions when your line will not break into two because of 80 characters per line rule (This is default for most IDEs).
UI vs Business logic
Keywords: #Redux #StateManagment
This is a very important topic and requires a lot of writing. So, I wrote an article about this one. This approach will reduce your code duplications, you will achieve flexibility and you will remove all unnecessary calls of setState()
method.
What’s next
The topics covered in this article are the basics for applying clean code principles inside your Flutter project. The methods shown in this article will increase your code readability and, at the very least, humans will understand what you are writing, not only computers. There are a lot more things that you should read and learn about clean code, so I strongly recommend ” A Code of Conduct for Professional Programmers” from Uncle Bob. But for now, keep your functions as small as possible, separate widgets per file, and write tests!
Example
We’ve developed mid-complexity project based on these principles, if you want to try it, please be free to download it and give us feedback.