This Is How I Maximize The Benefit of Flutter Modularization

fauzi nur
evermos-tech
Published in
5 min readMar 30, 2023

Modularization has been the common architecture in software development. It splits one app into smaller pieces of modules. Modularization enables the app to have faster performance, more maintainable. Modularization can even be more beneficial if you are working on a complex app with a bigger team. Since modularization offers high reusability in your app, you may want your module to be used by the other team in your company in different cases. This is how I maximize my feature module from modularization and is flexible enough to be used in many cases.

The App Architecture

Let me explain the project I’ve been working on. The project is about the features which let the user purchase various digital products such as electricity, insurance, internet subscription, and etc. Those modules are formed in a Flutter package or plugin. Since they are Flutter packages or plugins, they can be attached to any Flutter app and even Android and iOS native via Flutter Module. For now, the feature is attached to one app. Each feature at least consists of an inquiry page (where the user fills mandatory fields) and checkout page.

The user flow is very simple, users can fill the required field in the inquiry page, check the summary on the checkout page, continue the payment, and they can see their purchase receipt on the transaction history.

As you can see, there are three modules used. The digital_products module consists of digital product folders mentioned before. Each folder follows the Clean Architecture folder structure (presentation, domain, data). The same pattern is also applied to the payment and transaction_history module.

The main app connects all of them!

Although the three modules seem to be connected to each other, they don’t really communicate to each other directly. The checkout page on the digital_products module does not know that it is redirected to the payment after checkout process. The payment page in the payment module does not know that it is redirected to the transaction history page in the transaction_history module. How do they know each other, then? The main app plays a role in this case!

If you look again at the diagram above, all of the three modules are used on the main app. The main app gets all of the three modules and connects them. This is the correct place to redirect the user to the payment page after the user taps the checkout button on the checkout page. And also the correct place to redirect the user to the transaction history page after the user finishes the payment process. By applying this kind of pattern, each module can focus on what the module is supposed to work. The digital_product can only focus on receiving user inputs and showing summaries related to the transaction. The payment module can only focus on handling the payment business process. The transaction_history can only focus to show the user transaction history. This kind of pattern leads to cleaner code, easier to test, isolated changes, and more flexible.

Override them when needed

We have successfully developed a bunch of modules which work together with the help of the main app. However, there may be cases that take advantage of the core feature. Let’s say, your data team is interested to know how many users that taps the checkout button through the third party service. Where do you put that kind of requirement? inside the module or the app? Actually, both of them are correct. Let me explain both of them and their drawbacks.

If you are using the first approach, you can just create a new page which extends the checkout page, import the digital_product , and override the tap callback method. Done!. I personally prefer this approach because it prevents the module from doing something it should not do, which leads to cleaner code and clearer responsibility. But as a trade-off, you need extra time to override things you need on the main app.

You also can use the second approach by directly importing the third party service on the module and implementing it inside. This way, you don’t have to override many things on the main app. Simply import the package and use them. But you should be aware since the module now depends on the specific third party service, every implementation of the module must also implement the third party service (remember that the Flutter package or Flutter plugin can be used in multiple main apps). This approach benefits you more if you are only working on only one main app.

But, if you know you are going to use the module for different purposes such as deploy the module to the web, or the other team in your company want to attach your module to their apps. The third party services you use might not support a specific platform yet, so you want to throw them for a moment or the teams want to use the module, but not the services because they don’t need them. With the first approach, you and your team in your company can easily throw away the unnecessary third parties and customize the module as they need. It is because the third parties are only attached to the main app, not the modules.

In the end, I do still recommend the first approach regardless of the purposes. The approach takes your time more to override things, but it guarantees you to have cleaner, more flexible, and clearer module responsibility. Just consider this as an investment.

SOLID Principle behind the hook

If you notice the pattern, it’s all about separation of responsibility. The module only does the things it is supposed to do. It is similar to the Single Responsibility Principle in SOLID. In our module case, the single responsibility principle states that only one reason that can make one module change. The modules only focus on their jobs and not anything else. If there are modifications which make use of the core features, they can be implemented outside the modules (in our case, inside the main app). It is similar to the Open-closed principle in SOLID. In our module case, the open-closed principle is all about minding our own business and letting the client extend our feature for more modification. This leads to fewer test cases, lower coupling, and more focused modules. Imagine if there are several teams who want to use your module, but they have different requirements. You don’t want them to modify your feature module directly, right? You can let them extend your feature and implement the modification in their app.

References

  1. https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/

--

--