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.
What is modularized dependency injection in the context of
Flutter?
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.
What are the benefits of using modularized dependency
injection in Flutter apps?
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.
How does modularized dependency injection work in
Flutter?
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.
What are some popular dependency injection libraries or
frameworks for Flutter?
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.
How can developers implement modularized dependency
injection in their Flutter apps?
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.
What are some best practices for using modularized
dependency injection in Flutter apps?
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.
Are there any limitations or drawbacks to using modularized
dependency injection in Flutter?
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.
Where can developers find resources and tutorials for
implementing modularized dependency injection in Flutter?
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.
