What the Flutter module is, and why do we need them?
In short: the Flutter module is a collection of widgets (+ accompanied with business logic), grouped, on a high level, by the single common goal - the implementation of the specific functionality of the application.
For example, let's consider the Login module. We can define the following goals for such a module:
Implement it as a reusable and replaceable widget that can be used in many apps;
This module must be customizable to let the clients change the theme and style;
It must support form pre-validation (and the rules must be configurable);
It must support two types of signing in: log in using user name and password (or another set of fields - it should be configurable) or log in using local biometrical authentication (Touch ID/Face ID);
It should be configurable according to the client's needs;
We can think of more requirements - that's just an example.
In Flutter, everything, by the end, is a widget.
Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, in which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.
But we prefer to use the term module for the big chunks. Internally, the Login module can use other widgets (smaller building blocks) like FormBuilder, TouchIdButton, RaisedButton, etc (the collection of widgets mentioned above) + implement some controller logic: form validation, implementation of the sign-in states, etc.
Login module widget, as a state machine, may have the following states:
Empty form state
Form full-filled state
Form in-place validation error state (blocks the submit button)
Form submitting in-progress state (lock of the sign-in buttons, show somehow the progress of the submitting process, etc.)
Form submitting error state (display in-place errors if possible but let the client of the Login module define the error handling business logic)
Form successfully submitted state (some callback or transition to another widget, for instance - it depends on your needs).
All public interfaces of such a module must be developed according to the SOLID principles. By the public interfaces, we mean the module public interface for the configuration and customization of the business logic.
AuthApiService mixin (that's just an example, you can design it as you want)
(you need to define such public interfaces that will be used by your Login Module to drive the business logic, but the actual implementation can be configured outside).
What is SOLID?
A class should have only a single responsibility.
A software module should be open for extension but closed for modification.
Liskov substitution principle
Objects in a program should be replaceable with instances of their subtypes without altering the correctnesses of that program.
Interface Segregation Principle
Clients should not be forced to depend on the interfaces they don't use.
Dependency Inversion Principle
Depend on abstraction (interface), not on implementation.
So, the goal is to let the client of your module be able to configure the business logic of the widget. Also, it is typically required for the unit-testing.
Flutter is all about widgets and states, that's the best (from the 12 years of mobile development experience perspective) and productive way to effectively develop mobile apps with an enterprise quality. As a mobile developer, you should look for an opportunity to design your widgets as a composition of other widgets with a public interface for the configuration and customization.
As a result, you will have a hierarchy of widgets with clearly defined rules (interfaces) of communication between them.
FormBuilder (configured for Login)
FormBuilder (configured for Forgot Password)
FormBuilder (configured for Registration)
Main Screen Module
As you can see, some small building blocks (like FormBuilder and SubmitProgressButton widgets) are already reused even though it’s just a mental experiment.
So, the first thought, when you develop anything in mobile and especially in Flutter, must be - can I create it as an independent small widget that can be reused in many projects, and, what is more important, can it be easily replaced with an alternative widget?
As an addition to the SOLID principles, I'd like to recommend to think of additional criteria for your widget:
The beauty of Flutter architecture is in fact that widgets can be smart!
If in a long-term vision, you plan to develop many mobile apps, you must have a palette of reusable components - your building blocks... to quickly develop the most common functionality.
What does Smart mean?
So, let's get back to our virtual Login module. Obviously these days it is must-have to support the biometrical auth (if it is supported by the device), and most developers will add support of this feature directly into their login form controller, which may sound good, but, as a result, the logic of this button and everything related to the local biometrical auth will be the part of the login controller of the form. That's will be a violation of the Smart widgets principle. If you can encapsulate some specific functionality inside your widget you should do it!
It is better to develop a new widget, for instance, named TouchIdButton. This widget will check if the user has already logged in before and biometric login was authorized (how? is configured outside by the concrete implementation of the provided by the widget public interface). Also, it will encapsulate all the logic related to dealing with Android/iOS platform-specific differences. This button will know which icon to display: TouchID or FaceID. As an additional public interface, you can define another required controller to handle the onTap event and let some business logic written outside of your widget to disable you, etc.
So, as a result, it can be inserted in any login form!
And now we come to the beauty of Flutter and, in general, an example of why widget can be Smart! If the device does not have the support of Touch ID or Face ID your widget can return itself as a child of Visibility widget!
So, if in your Login Module you will wrap it with Row widget something like this:
if biometry is not supported on the device, in general, or the user never enabled it, you will have the following result automatically!
So, a big amount of logic not directly required for the Login Module to take care of will be encapsulated by the small but smart widget TouchIdButton.
The same with a form of fields, you could develop a separate smart widget who knows how to build forms, how to validate fields, which let you configure the validation rules by the public interface. Which knows how to display validation errors, hints, supports the right keyboard types for the right fields, etc... As you can see it is also a huge amount of work... and Login Module should not take care of it!
What does Customizable mean?
The first part of this principle is already covered above: a good module or widget must have a public interface to control its behavior. The second aspect of it is visual customization, and that's btw an issue with many existing cool material widgets already available in Flutter, ideally, you need to stick and use the style parameters provided for you by the Theme widget, so use the colors, fonts, etc from the Theme. Also, it helps to let your users customize the exact size of your widget or implement it in a way to always fill the parent. All internal magic numbers must be configurable (like internal paddings between elements) if it is applicable for your widget. As a possible solution for this, you can define your own Inherited Theme Widget - shared between all your widgets (with some basic Design System settings) and always use the numbers defined by it and only some magic numbers as defaults for the fallback (if your custom Theme widget is not available in the widgets tree).
What does a Replaceable mean?
Keep it simple! The typical mistake of the mobile developers (especially developers who previously developed backend solutions) they love to overcomplicate things. Like designing of many layers of abstraction, interfaces of interfaces, some abstract template Fabrics to instantiate them, etc. In most cases (in practice we never faced with this for 12 years), you will never have multiple implementations of your beautiful abstract interfaces of interfaces... Do not overcomplicate everything if it is not really required. You should use interfaces ideally only in some concreate specific places when you want to reduce the dependency. Back to the Login Module, you will obviously use this module in different projects with different backends, so the exact logic of the login API calls must be implemented outside of your module. That's why you will define some simple mixin to cover this and let your clients configure the Login API Service outside. But you should not create a Fabric of Abstract Template Login Modules generators...