The 2 keys to Modularization in Flutter: Classes and Parameter Methods
I want to share a lesson I find very interesting. When developing in Flutter, which is inherently modular (since everything in Flutter is a widget), we can fall into the false belief that our application, however small or medium-sized, will automatically be modularized.
In my opinion, it's quite the opposite. Given how easily, for example, Visual Studio Code allows us to start nesting widgets, we can easily break the modularity of our application. This, as you know, creates serious problems: we lose scalability, maintainability, and the code becomes spaghetti code.
If this is for an end user who plans to continue developing the application, the next developer won't understand anything we're doing, as the code is very difficult to follow.
First Tip: Methods vs. Classes for Returning Widgets
This is the context for why modularization is crucial. Here's my first practical tip: when to use methods and when to use classes (like a StatelessWidget) to return content.
I would use methods to return a widget if the component is extremely simple. For example, for a simple posts component like the one we have:
- We have an image.
- We have some text.
- We have the title.
In this case, I would perfectly use a method to easily reuse that widget.
Class modularization
The widget we are going to analyze is the web viewer of the Academia app for Flutter/Android, which has the following elements, that is, the first thing we must do in these cases is functionally divide what we want to implement, in this example, a native viewer with options for selecting text and creating notes.
- Translate HTML: HtmlToWidgets
- Select text and display a bubble: CustomTextSelectionOverlay
- Create notes: CreateBookNoteForm
These are the modules in our app, and they need to receive only the data needed. Here's my class-based implementation:
HtmlToWidgets(
html: bookSectionModel.content,
sizeFactText: sizeText,
htmlNoteBook: htmlNoteBook,
callback: noteAdd,
setKeys: setKeys,
),
***
class HtmlToWidgets extends StatelessWidget {
***
/*const*/
HtmlToWidgets({
super.key,
required this.html,
required this.sizeFactText,
this.callback,
this.setKeys,
this.htmlNoteBook = const {},
});
***
_hightlightText(
" ${element.text}",
element.attributes['id'] ?? idChild2,
16 * sizeFactText,
context,
htmlNoteBook[element.attributes['id'] ?? idChild2],
'SUSE'
),
***
Widget _hightlightText(
String text,
String idHTML,
double fontSize,
BuildContext context,
HtmlNoteModel? htmlNoteBook, [
String fontFamily = 'IBMPlexMono',
]) {
***
if (tokenText == "") {
return CustomTextSelectionOverlay(
text,
tokenText,
idHTML,
fontSize,
callback!,
// () => callback(id, tokenText),
);
}
}
}class CustomTextSelectionOverlay extends StatefulWidget {
***
}
class _CustomTextSelectionOverlayState
extends State<CustomTextSelectionOverlay> {
***
child: CreateBookNoteForm(saveNote),As you can see in the code above, the important thing is to see the class signatures. It receives what it needs to perform its work, and for the final operations, it is sent to the function as a parameter.
An example of a flawed implementation, where a function receives parameters it will NOT use directly:
List<Widget> showSectionBookByHTML(
String html,
String tokenUser,
int bookSectionId,
Function noteAdd,
BuildContext context, [
Map<String, BookSectionNoteModel> bookSectionNotesModel = const {},
double sizeText = 1.0,
]) {In the previous example, which is a flawed implementation, not using classes not only complicates management by having a bunch of classes at the same level with different names, but also receives parameters that aren't specifically used by the function; for example, the user notes function.
What the function does is display the HTML content, but the user comments in the notes are NOT a direct feature of the viewer; they are an add-on or optional feature of a native viewer.
Thanks to modularization with classes, we pass the following methods as parameters:
HtmlToWidgets(
html: bookSectionModel.content,
sizeFactText: sizeText,
htmlNoteBook: htmlNoteBook,
callback: noteAdd,
setKeys: setKeys,
),
I agree to receive announcements of interest about this Blog.
Modularize your Flutter app to avoid spaghetti code. Learn when to use classes (StatelessWidget) versus methods to reuse widgets, and how the Single Responsibility Principle (SRP) applies by passing only the strictly necessary data and functions, ensuring scalability and maintainability.