Skip to content

Flutter: Modularized Dependency Injection

Document

Flutter: Modularized Dependency Injection

Let’s say that you’re in the phase where you’re Maintainability of of your Flutter project is a
crucial aspect to consider, so it is important to make sure your project is adhering for the best
practices to ensure a suitable structure and code quality and maintain it at a level that is
satisfactory.In that situation, Separation of Concerns , Encapsulation , coupling , and cohesion
These are the aspects you’d like to control in the architecture you choose to build.

It’s better to choose physical separation instead of logical, i.e. break your project up into
Dart/Flutter packages instead of simply grouping things into various directories. If you have a tiny
Flutter app that is merely the logical separation, you’ll fail to transform the app into physical
files that reflect the directory structure you have. This is often due to the fact that it’s easy to
violate or ignore architectural restrictions even when there’s no physical separation.

Dependency Injection in modules? How?

It’s obvious that the precise
architecture will depend on the project, team and knowledge. I’m not planning to talk
about architecture within the context of this article, but instead focus on the way you
can arrange Dependency Injection (DI) in the form of a modularized Flutter application.

Refactoring The CounterApp

It’s obvious that the precise
architecture will depend on the project, team and knowledge. I’m not planning to talk
about architecture within the context of this article, but instead focus on the way you
can arrange Dependency Injection (DI) in the form of a modularized Flutter application.

Cross-Cutting Concerns

A cross-cutting concern package generally contains items that impact the entire program and is able
to be utilized by all layers. I added DI abstractions in it.

The first is known as DI This interface is accountable for retrieving objects from the DI container.

                    
                        abstract interface class DI {
                            T call({String? instanceName});
                            T get({String? instanceName});
                            T getWithParam(
                            P param, {
                            String? instanceName,
                           });
                           }
                         
                    

The other interface is called DIRegistrar and offers the API for registering dependencies within DI
Containers. DI Container. This interface should be accessible only to the implementation and not to
its abstraction as well as to the ModuleDependencies abstraction and implementation.

                    
                        typedef FactoryFunc = T Function();
                        typedef FactoryWithParamFunc = T Function(P param);
                        typedef DisposingFunc = FutureOr Function(T instance);
                        abstract interface class DIRegistrar implements DI {
                        void registerFactory(
                        FactoryFunc factoryFunc, {
                        String? instanceName,
                        });
                         void registerFactoryWithParam(
                        FactoryWithParamFunc factoryFunc, {
                         String? instanceName,
                        });
                        void registerSingleton(
                         T instance, {
                        String? instanceName,
                        DisposingFunc? disposingFunc,
                         });
                        void registerLazySingleton(
                        FactoryFunc factoryFunc, {
                        String? instanceName,
                        DisposingFunc? disposingFunc,
                         });
                        }
                        
                    

The third part of the puzzle is ModuleDependencies. It must be implemented by all the modules that
have dependencies.

                        
                            abstract class ModuleDependencies {
                            Future register(DIRegistrar do);
                            Future runPostRegistrationActions(DIRegistrar do) => Future.value();
                            }
                            
                        

DI Abstractions Usage

The modules are designed to contain the implementation details and provide only the information
essential.

For instance , the reason I included Flutter bloc state management into the presentation package is
a design feature, which means it can be changed within this package without needing to alter any
other packages in any way.

                    
                        class PresentationModuleDependencies extends ModuleDependencies {
                        @override
                        Future register(DIRegistrar do) async {
                        di.registerFactory(
                        () => CounterCubit(di()),
                         );
                        }
                        }
                         
                    

With the data package, I chose to implement shared_preferences to maintain the status of the counter
in between app starts. It is also “known” only to the data package.

                        
                            class DataModuleDependencies extends ModuleDependencies { 
                            @override
                            Future register(DIRegistrar do) async {
                            final sharedPreferences = await SharedPreferences.getInstance();
                            di.registerFactory(
                            () => SharedPreferencesCounterRepository(sharedPreferences),
                            );
                            }
                            }
                            
                        

The application will,naturally, be able to have transitive dependency upon shared_preferences and
Flutter_bloc in the end since this is a given and is designed to be part of the base package that
eventually combined everything into one artifact. e.g. ipa, apk.

In the initialization phase of our application look through all the installed modules and instruct
that they should register their dependencies.

                            
                                Future main() async {
                                WidgetsFlutterBinding.ensureInitialized();
                                final de = GetIrDI();
                                final modules = [
                                DomainModuleDependencies(),
                                PresentationModuleDependencies(),
                                DataModuleDependencies(),
                                ];
                                for (final module in modules) {
                                await module.register(di);
                                }
                                for (final module in modules) {
                                await module.runPostRegistrationActions(di);
                                }
                                runApp(App(di: di));
                                }
                                
                            

You might have been able to see the “GetItDI” program in the line of code below. The implementation
is of DIRegistrar which I added to the application package. This implementation is based on get_it,
which is the receive_it package. If you decide to change to a different DI Container it is possible
to do it as simply as changing the design for DIRegistrar within the application layer, without
impacting different packages.

The concept should be evident to you now however, you’re encouraged to look around this repository
for the remaining pieces!

Table of Contents

Tags Cloud

Angular Developers
Angular Development Company
Angular Development Services
ASP.Net Application Development
ASP.NET Boilerplate Development
Company

ASP.NET Boilerplate Development
Services

ASP.Net Core Development Services
ASP.Net Developers
ASP.NET Development Advantages
ASP.NET Development Services
ASP.NET Development Solutions
ASP.Net
MVC

ASP.Net
Programmers

ASP.Net Zero
ASP.Net Zero
Developers

ASP.NET Zero
Development Services

C Sharp Developers
C Sharp
Development

C# Developers
C# Development
Company

C# Development
Services

Neo Infoway
Custom Application
Development

Custom Software Development
Solution

Hire .Net Developers
Hire Angular Web and App Developers
Hire ASP.Net Developers
Hire SharePoint Developers
Ideas Software
Kentico Development Company
Kentico Development Services
Kentico Web Developer
Responsive Web Design
SharePoint Developers
SharePoint Development Services
UI Designer
UI/UX Design Services
Umbraco Development Company
Umbraco Development Services
UX Designer
Web Design Services
Web Design Solutions
Web Designers
Web Designing
Website Design Agency

Frequently Asked Questions (FAQs)

What is
dependency injection (DI), and why is it important in Flutter app development?

Dependency injection is a design pattern used to manage the dependencies
of objects within an application. In Flutter app development, DI helps decouple components,
improve code maintainability, and facilitate testing by allowing dependencies to be provided
externally rather than being hardcoded within classes.


Modularized dependency injection in Flutter involves organizing the
application into separate modules or features, each with its own set of dependencies and
services. This approach allows for better separation of concerns, easier code organization,
and more flexible dependency management.


Some benefits of using modularized dependency injection in Flutter apps
include:

  • Improved Code Organization: Modularization allows developers to organize code into
    smaller, more manageable modules, making it easier to understand and maintain.
  • Flexible Dependency Management: By breaking the application into modules, developers
    can manage dependencies more granularly, allowing for easier updates, substitutions,
    and testing of individual components.
  • Reduced Coupling: Modularized dependency injection reduces coupling between
    different parts of the application, making it easier to change or replace components
    without affecting other parts of the codebase.
  • Scalability: As the application grows, modularization enables developers to add new
    features or modules without impacting existing code, promoting scalability and
    extensibility.

In Flutter, modularized dependency injection typically involves using
dependency injection containers or service locators to manage dependencies within each
module or feature of the application. Each module defines its own set of services and
dependencies, which can be provided and accessed within the module or shared with other
modules as needed.


Some popular dependency injection libraries or frameworks for Flutter
include:

  • get_it: A simple service locator for Dart and Flutter that allows for easy
    registration and retrieval of dependencies.
  • provider: A popular state management library for Flutter that also provides
    dependency injection capabilities through its Provider class.
  • injector: A lightweight dependency injection library for Dart and Flutter that
    supports modularization and lazy loading of dependencies.

Developers can implement modularized dependency injection in their
Flutter apps by:

  • Identifying and defining separate modules or features within the application.
  • Deciding on the scope and lifecycle of dependencies within each module.
  • Using a dependency injection library or framework to register and provide
    dependencies within each module.
  • Injecting dependencies into classes or widgets as needed using constructor injection
    or other DI patterns.


Some best practices for using modularized dependency injection in
Flutter apps include:

  • Keeping modules small and focused on a single responsibility.
  • Avoiding circular dependencies between modules.
  • Using named or tagged dependencies to differentiate between similar services within
    a module.
  • Testing each module in isolation to ensure that dependencies are correctly provided
    and injected.


While modularized dependency injection offers many benefits, it may
introduce some complexity, especially in larger applications with many modules and
dependencies. Developers should carefully consider the trade-offs and design decisions when
implementing modularized dependency injection to ensure that it aligns with the needs and
goals of the project.


Developers can find resources and tutorials for implementing modularized
dependency injection in Flutter on official documentation provided by Flutter and Dart,
community forums like Stack Overflow and GitHub, developer blogs and tutorials, online
courses and webinars, and sample projects and code repositories. Additionally, exploring
Flutter packages and plugins specific to dependency injection can provide additional
insights and guidance for implementation.

Projects
0 +
Clients
0 +
Years of Experience
0 +
Startups
0 +

WANT TO START A PROJECT?