Hello devs,

I always liked this “Open Source” footprint to the point of publishing everything I create to use, and it has already generated a lot of joy for me, I almost lost a contract for publishing a certain code in the community (I’ve always been kind of stupid).

It was then that I discovered Flutter as a Baby and soon I was missing some things that I was used to using in other frameworks such as DI (Dependency Inject) and a more dynamic routes control.

When BLoC reached the community, we had to work with InheritedWidget to provide the BLoC class for the entire application, because using Singleton was totally out of the question due to the problems we had when adopting this practice. Today I’m not going to talk about Singleton’s ills in its overuse, but just to contextualize, to detach a code from the tree, it harms the tests, violates the principle of exclusive responsibility, because it has no life cycle and also ends up not having its dependencies exposed, in addition, there may also be errors that you will never notice at the beginning of the application, but when climbing (if you can climb) it will be your worst nightmare. Singleton has been completely discarded in order to access a Block, (But you can still use your singleton with caution).

InheritedWidget was widely used in the beginning to meet the goal of providing a BloC class for each application, but few knew a good way to implement it, so I decided to create a generic BlocProvider and therefore bloc_pattern, the first generic BLoC provider, was born , or any other class.

Then came other solutions with the same objective, for example Provider and other generic bloc_pattern forks.

You may be wondering, if bloc_pattern came before the Provider, why doesn’t Google support the Brazilian package? Here begins the moment when the Brazilian community has evolved.

With the use of bloc_pattern some problems with InheritedWidget were discovered but which are not considered a problem. I already talked about it in this other article where I explain why I went “against the tide” even though I was a pioneer in the use of Generic Providers and did not use the Provider.

So, we needed something else to do a dependency injection, so we started using a singleton concept with StatefulWidget and, thus, we got an injection independent of the widget tree, but with its own life cycle, and thus an injection of dependencies. We will see.

But why am I talking about bloc_pattern and not flutter_modular? Simple, the flutter_modular is actually an update to the bloc_pattern, completely detaching itself from the BLoC name and serving anything that needs a dependency injection. However, this is not just a name change, obviously the entire Core dependency injection has been reused, but there were two problems, one serious problem and the other that comes with Flutter Web, they are: Modular Reuse Codes and NAMED ROUTES!

I never said anything I hate about Flutter, so here it goes … I hate Flutter’s named route system, I think they’re disorganized when the app grows and works more like an ALIASES, so I don’t have to keep writing, MaterialPageRoute every time i need to call up a new screen. However, with the arrival of Flutter Web, this would get even worse, as we often need fully dynamic URLs or we would always have to use dynamic links. A problem led to another, so i decided to create a dynamic and modular route system.

My opinion is that Flutter was also made for beginners and, therefore, offers a simple route system, simple state management and an even simpler structure, but everything becomes a big mess when the application grows and this is the most serious problem that I highlighted above. the maintenance and reuse of code of a structure necessary to work in teams without your git complaining about conflicts.

Imagine that you are creating your entire project with setState and a simple MVC structure, delivering it to your client and he is happy; it asks you to make a change and with each new feature that you deliver, 2 new bugs are generated and this increases over time! Until you are completely discouraged from continuing on this project because of so much disorganization that you caused yourself. I tell you, there is nothing worse than working on an unstructured project.

I tell you, there is nothing worse than working on an unstructured project.

I believe that you already understood my main motivations in creating a package that helps you, especially in structuring your project. So, let’s get to know Modular.

Getting to know Modular

I have already created several videos about Modular, but I never explained my motivations and I can tell you that there are others, but nothing beats the organization of the structure of the Project with Modular.

The modular Flutter works with dependency injection and dynamic routes in each module of your project, that is, you can create several sessions responsible for a given resource and place only the injections and routes that you will use in this session/module. For example: You have a login module, this module can have some controllers or blocks and the screens responsible for making the login happen as a user registration screen, another by phone and so on. The great advantage is that you can have several modules in parallel to this one and, thus, work with your team without worrying about conflicts; in addition, if you want to perform a maintenance on the login screen for example, you will know where to find it (in the login module). This makes everything obvious and more readable when the project is scaled and you also save time on reusing code on a modular level.

In addition, each injection (which I will call Bind) is destroyed when you exit the module, that is, when there is no more screen for that module open, it will be destroyed.

With this, you will not have Singletons in your memory all the time and will also use only what is necessary for your system to work.

That said, we will see how the modules work in flutter_modular.

That said, let’s see how the modules work on the flutter_modular.

Dividing your project into Modules

Modular has 3 types of Modules, the main module, responsible for the entire application, the children that will be added to the parent and a third special type to work with the Modules in the Widget, if you use BottomNavigatorBar or Drawer, for example.

MainModule

MainModule is simply any application. Connections and routers will work throughout the application. Basically, everything that is placed in the Binds, can be accessed in any other module. We usually use this injection when we want the class to be accessed from anywhere in the application.

This module also needs a “bootstrap”, which in this case would be the main widget of the application, usually the one that contains the MaterialApp.

Now, to start the application as a module, just use ModularApp in main().

ChildModule

You can nest other modules in the MainModule, thus creating a tree of Modules totally independent from each other, with their own Binds and routers, that’s why we call ChildModule, and can be a child of MainModule or even another ChildModule.

ChildModule does not need a main widget, it only has its Connections and Routers Matrix. The moment you enter a route for a child module, it is activated and when you exit this screen, the module is destroyed.

For ChildModule to work, you must add it somewhere, such as in AppModule or another module on routers.

Router (‘/home’, module: HomeModule()),

If you place HomeModule in AppModule with the path ‘/home’, all routes in the child module will be concatenated with ‘/home’.

Looking at the example, if you want to access the route bar of the child module, use the path: ‘/home/’

And if I need to access the ListWidget, use: ‘/home/list’

ModuleWidget

If a TabBar or Drawe has an independent Route Navigation, you can use ModuleWidget to have a modular structure in an environment without named routes.

In this module you have the Binds and the View which is the main Widget. To use this module just call it as any Widget.

Dependency Injection

Modular was designed to work with a dependency injection system similar to that used in other structures. The ideal was to receive the instances directly in the controllers’ builders, however, this would require a build_runner, since the Flutter Dart has no Reflections, so we use the Bind object to “simulate” the injection directly into the Class’s constructor.

Bind ((i) => MyClass ());

Bind has this structure because it can receive the injection during the call of classes and also provides “Lazy Loading”, that is, the class is instantiated only when it is called for the first time, after that it starts to obey certain parameters, which Singleton pattern does during the lifetime of the module that has been implemented !. See an example of this configuration below:

@override

List <Bind> get binds => [

Bind ((i) => HomeController (repository: i.get <ApiRepository> ())),

Bind ((i) => ApiRepository (dio: i.get <Dio> ())),

Bind ((i) => Dio ()), ];

When the HomeController is called, it automatically receives the ApiRepository, which in turn receives an instance of Dio and, therefore, we have the injection of completely dissociated dependencies.

The anonymous function that Bind receives has a parameter called (i) of Injection and has some useful methods to execute when the class is requested, such as i.get (), which looks for other injections by the type passed in generic<>.

The injection also has another property called i.args, which is used to retrieve the parameter transmitted by the route / URL (we will see later).

To call a Bind and work on the screen, just use:

Modular.get <HomeController>();

This will perform all injections and bring the object already configured.

Why use dependency injection?

We use it to promote low coupling, as in the example above. You can change your repository just by replacing the object in the constructor, HomeController. This for unit tests is interesting, because we can play a Repository and instantiate our HomeController constructor.

Setting up a Bind

You can configure how you want to build your object called by dependency injection, for that you just need to use some Bind parameters.

Bind ((i) => HomeService (), singleton: false)

Deactivating the singleton, the HomeService will have a new instance for every call to Modular.get.

Bind ((i) => HomeService (), lazy: false)

By default, the modular only instantiates a bind when it is called for the first time, and this is called lazy. You can change this behavior so that the object instantiates when the Module is started. This is useful if you have a process independent of a view and need to start it together with the module even when it is not called.

Configuring Routes

We come to a very important topic.

With the adoption of Flutter Web, the developer Flutter has to think that his application will work on the web, which makes it easier to work with named routes, as we can modify the url in the browser to access some other screen. this opens up possibilities for SEO and Direct Links, so we decided to adopt NAMED ROUTES as a standard for projects.

You have two types of routes, daughters and module concatenation.

@override

List <Router> get routers => [

Router ("/", child: (_, args) => HomePage ()),

Router ("/ login", child: (_, args) => LoginPage () ),

];

In the example above, we see two child routes that, when accessed, display the respective pages.

In the anonymous function of the child we have the context and the args object as parameters.

We can use args to retrieve a parameter passed by url dynamically:

Router ("/ product /: id", child: (_, args) => Product (id: args.params ['id'])),

In this example, we pass a route with a dynamic value called : id , and retrieve it on the page with args.params [‘id’];

Args can also receive entire objects passed through the arguments parameter of the Navigator:

Modular.to.pushNamed ('/ product', arguments: ProductModel ());

getting on the route

Router ("/ product", child: (_, args) => Product (model: args.data)),

So far these are all Modular weapons and now we can think about the structure of the project itself.

Recommended project structure

Each Module can have several pages with their respective controllers, repositories, services, stores or blocks, the important thing is to think about the total scope of your feature and have everything within a single module.

Today we recommend the use of MobX as its state management, not only because of the ease of the Stores but also to use its reactive structure also in the controllers and thus connect the Stores and pass the information clean to the view.

With that we have a file structure similar to this:

Module

By default, a module must have 3 files, a controller, a view (page or widget) and a file for your module (ChildModule).

Pages

A Module can have several pages and each page may or may not have a controller.

A page is considered a stateful widget that covers the entire navigation screen.

example files:

home_page.dart

home_controller.dart

You can use ModularState instead of State on your page to automatically retrieve the page’s controller.

Using ModularState you get the variable controller which is the injection of the page control. It is worth mentioning that ModularState destroys the controller when the page is destroyed. This behavior exists to make the view and the controller the same component taking advantage of the injections.

Widgets

They are components that represent a page block. They are usually stateless and may or may not have a controller.

Repository

They are objects responsible for executing connections with APIs and must be injected into controllers or stores. It is not good for a controller to execute a rule that accesses the internet or an internal component such as a database, for this use Repository or Services.

Services

Used to broker a connection with components or hardware such as Bluetooth, database, GPS, etc …

Models

Whenever you think about data manipulation, think about model. He is responsible for reading and writing of data, and also of their validation .

Automating

Now that we have a well-defined, testable and scalable project structure, we can think about automating some of these processes. For this we use a CLI called Slidy, responsible for generating templates with this structure.

To install and use Slidy access this link .

Finishing the post but not the studies…

Now that we’ve talked about structures and we have all the tools to enable a scalable and modern project, don’t stop there, put it into practice right now. If you are interested and want more information access the official documentation of flutter_modular by clicking here .

Don’t forget that we have two series that can help you a lot on the Flutter channel.

I hope you enjoyed it! Leave in the comments what you think and do not forget to comment in the group your opinions. We have an exclusive channel for Slidy/Modular on Discord. Hugs and LET’S BE REFERENCE TOGETHER!