Flutter GetIt
This package is an essential tool for efficient dependency management in your Flutter project's lifecycle. It offers robust support for page control, including route management and the flexibility to work with modules.
Key Features:
Dynamic Dependency Control: Utilizing the powerful get_it mechanism, this package automatically registers and removes dependencies as needed, optimizing performance and ensuring your application's efficiency.
Flexible Modules: Take advantage of your code's modularity. This package makes it easy to create and manage modules, keeping your project organized and easy to maintain.
Additional Benefits:
Automatic Dependency Cleanup: The package automatically removes dependencies when they are no longer needed, ensuring efficient resource management for your application.
Flutter GetIt offers various approaches to controlling routes and loading your application's bindings, including page routes, builders, and modules. You will see details of each of them later on.
Getting Started
Configuring flutter_getit
Setting up Flutter GetIt is done by adding a widget around your MaterialApp. By including the widget and implementing the builder attribute, three attributes will be passed to you:
Field | Description |
---|---|
context | BuildContext |
routes | A map that should be added to the routes tag of the MaterialApp or CupertinoApp |
isReady | This attribute indicates whether all asynchronous middlewares and bindings have been loaded |
The routes
attribute should be passed to the MaterialApp, as illustrated in the example below:
import 'package:flutter/material.dart';
import 'package:flutter_getit/flutter_getit.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return FlutterGetIt(
modulesRouter: [
FlutterGetItModuleRouter(
name: '/Initialize',
pages:[
FlutterGetItPageRouter(
name: '/Landing',
builder: (context) => const Scaffold(
body: Center(
child: Text('Initializing...'),
),
),
),
]
),
],
builder: (context, routes, isReady) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: '/Landing/Initialize',
routes: routes,
builder: (context, child) => switch (isReady) {
true => child ?? const SizedBox.shrink(),
false => const WidgetLoadDependencies(),
},
);
},
);
}
}
The example above demonstrates the basic configuration of FGetIt, including handling loading (loader) in asynchronous middlewares and bindings. Further down, we will detail each of these items.
Important: Flutter GetIt does not replace Flutter's standard routes; it leverages the existing structure using Flutter's native lifecycle. This approach keeps the application's navigation intact, avoiding unnecessary rewrites and preventing bugs and unwanted issues.
However, for it to control dependencies, you must register your application's pages in the modulesRouter
attribute as shown in the example above, pagesRouter
, or modules
, which you will see a little further on.
FlutterGetItModuleRouter
In the example above, you saw the simplest way to implement a route within flutter_getit. If your page is as simple as our initial page, you can use the page class, adding the page and the path it will respond to.
The first level of the route is FlutterGetItModuleRouter, which is responsible for grouping the routes of a specific area of the application, such as the authentication area, the products area, the settings area, etc.
FlutterGetItModuleRouter consists of a name
and pages
, where name
is the name of the module and pages
is a list of FlutterGetItPageRouter or other FlutterGetItModuleRouter.
You can think of FlutterGetItModuleRouter as a "Module" or "Sub-Module" within the routing system, allowing you to create as many modules as you want and nest them.
They are passed hierarchically to the child module or page, allowing you to navigate within the tree, and if necessary, FlutterGetIt will instantiate the Binds.
Let's look at an example of how to create a FlutterGetItModuleRouter:
FlutterGetItModuleRouter(
name: '/Register',
bindings: [
Bind.lazySingleton<RegisterController>(
(i) => RegisterController(),
),
],
onInit: (i) => debugPrint('hi by /Register'),
onDispose: (i) => debugPrint('bye by /Register'),
pages: [
FlutterGetItPageRouter(
name: '/Page',
builder: (context) => RegisterPage(
controller: context.get(),
),
bindings: [
Bind.lazySingleton<RegisterController>(
(i) => RegisterController(),
),
],
),
FlutterGetItModuleRouter(
name: '/ActiveAccount',
bindings: [
Bind.lazySingleton<ActiveAccountPageDependencies>(
(i) => (
controller: ActiveAccountController(name: 'MyName'),
validateEmailController:
ValidateEmailController(email: 'fgetit@injector'),
),
),
],
pages: [
FlutterGetItPageRouter(
name: '/Page',
builder: (context) => const ActiveAccountPage(),
bindings: [],
),
],
),
],
),
In the structure above, the FlutterGetItModuleRouter /Register
has a page /Page
and a submodule /ActiveAccount
, which in turn has a page /Page
. If you decide to navigate to /ActiveAccount/Page
, FlutterGetIt will instantiate the dependencies of /ActiveAccount
and /ActiveAccount/Page
and also of /Register
, as it is a superior FlutterGetItModuleRouter in the same tree as /ActiveAccount
.
A FlutterGetItModuleRouter can have as many pages and submodules as desired and can be nested with other FlutterGetItModuleRouter. The attributes of this component are:
Attribute | Description |
---|---|
name | Module name. |
bindings | Binds that will be instantiated and removed in the same lifecycle as the module used. |
onDispose | Here you can execute an action before the Binds are closed and removed. |
onInit | Here you can execute an action before the Binds are instantiated. |
pages | Pages or submodules that will be instantiated and removed in the same lifecycle as the module used. |
FlutterGetItPageRouter
FlutterGetItPageRouter is the component responsible for creating a page route within your application. It consists of a name
, which is the path of the route, and a builder
, which is the widget that will be displayed on the screen and also has Binds
to initialize and pages
if you want to create a hierarchical navigation tree.
FlutterGetItPageRouter(
name: '/Landing',
builder: (context) => const Scaffold(
body: Center(
child: Text('Initializing...'),
),
),
),
In the example above, we created a route called /Landing
that will display the text "Initializing..." at the center of the screen.
FlutterGetItPageRouter also has a bindings
attribute, which is a list of Bind that will be instantiated and removed in the same lifecycle as the page
used.
FlutterGetItPageRouter(
name: '/Landing/Initialize',
builder: (context) => const Scaffold(
body: Center(
child: Text('Initializing...'),
),
),
bindings: [
Bind.lazySingleton(
(i) => InitializeController(),
)
],
),
In this way, flutter_getit will add, during your screen's loading, an instance of InitializeController to get_it, enabling the use of your page. However, it is important to note that when leaving this screen, flutter_getit will remove the instance from your application's memory, ensuring efficient resource management.
FlutterGetItPageRouter also has a buildAsync
method that will be invoked when any asynchronous binding or middleware is found on this route.
Application Dependencies
Every project requires dependencies that need to remain active throughout the application, such as RestClient (Dio), Log, and many others. For FlutterGetIt, you can easily make these available. During the initialization of FlutterGetIt, simply pass the bindings
parameter within FlutterGetIt in the main function.
Example using bindings
FlutterGetIt(
bindings: MyApplicationBindings(),
builder: (context, routes) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: '/Landing/Initialize',
routes: routes,
);
},
);
The MyApplicationBindings
class extends ApplicationBindings, as shown in the example below.
class MyApplicationBindings extends ApplicationBindings {
@override
List<Bind<Object>> bindings() => [
Bind.singletonAsync(
(i) async => SharedPreferences.getInstance(),
),
];
}
modules
Attribute
In large projects, the list of application dependencies can be extensive. To keep the project more organized, we created FlutterGetItModule, which should be applied to the "modules" attribute. With it, you can provide a class for loading your dependencies and apply initialization rules, similar to routes.
FlutterGetIt(
modules: [
LandingModule(),
],
builder: (context, routes) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: '/Landing/Initialize',
routes: routes,
);
},
);
How to Create a Module:
class LandingModule extends FlutterGetItModule {
@override
String get moduleRouteName => '/Landing';
@override
List<Bind<Object>> get bindings => [];
@override
List<FlutterGetItModuleRouter> get pages => [
FlutterGetItModuleRouter(
name: '/Initialize',
bindings: [],
pages: [
FlutterGetItPageRouter(
name: '/Page',
page: (context) => const InitializePage(),
bindings: [
Bind.lazySingleton<InitializeController>(
(i) => InitializeController(),
),
],
),
]
),
FlutterGetItModuleRouter(
name: '/Presentation',
bindings: [],
pages: [
FlutterGetItPageRouter(
name: '/Page',
page: (context) => const InitializePage(),
bindings: [
Bind.lazySingleton<InitializeController>(
(i) => InitializeController(),
),
],
),
]
),
FlutterGetItPageRouter(
name: '/Simple',
page: (context) => const InitializePage(),
bindings: [
Bind.lazySingleton<InitializeController>(
(i) => InitializeController(),
),
],
),
];
@override
void onDispose(Injector i) {}
@override
void onInit(Injector i) {}
}
About moduleRouteName
:
- This is the name of your module; your routes should start with this key and end with the key of an existing internal route in
pages
.
About bindings
:
- This is where your module's "global" bindings are placed, such as repositories. Whenever a module route is called, these bindings will be checked and instantiated if necessary.
About pages
:
- This is where your module's internal pages are defined, following the same rules mentioned above about FlutterGetItModuleRouter. It is important to note that the order of the list does not affect usage.
About onInit
and onDispose
:
- These will be called at the initialization and disposal of the module (entry and exit), which can be any within
pages
.
Retrieving an Instance
There are two ways to retrieve an instance from flutter_getit. One is through the Injector class, and the other is through an extension added to BuildContext using context.get
.
Injector.get<ServiceForApplication>();
// or
context.get<ServiceForApplication>();
Example Using context.get
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
// Calling the BuildContext extension
var service = context.get<ServiceForApplication>();
return ...;
}
}
Example Using Injector
class HomePage extends StatelessWidget {
final service = Injector.get<ServiceForApplication>();
HomePage({super.key});
@override
Widget build(BuildContext context) {
return ...;
}
}
- You can also use Injector.getAsync for asynchronous Binds; see more about them below.
Here's the translation of the provided text:
Bind Types
Flutter_getit supports all other bindings supported by the get_it engine:
Here is the table formatted in Markdown:
Bind | Description |
---|---|
Bind.lazySingleton |
This bind will initialize the dependency only when the user calls it for the first time. After that, it will become a singleton, returning the same instance every time it is requested. |
Bind.lazySingletonAsync |
This bind works like Bind.lazySingleton , but its first call will be asynchronous. |
Bind.singleton |
Unlike lazySingleton , singleton will initialize the instance immediately when the page loads. |
Bind.singletonAsync |
This bind works like Bind.singleton , but its first call will be asynchronous. |
Bind.factory |
A factory ensures that every time you request an instance from the dependency manager, it will provide a new instance. |
Bind.factoryAsync |
This bind works like Bind.factory , but its first call will be asynchronous. |
keepAlive
Attribute
- The
keepAlive
attribute, present in bindings, instructs FlutterGetIt not to discard the class, keeping it in memory throughout the application's lifecycle.
It's important to use this parameter cautiously and only when you are absolutely sure of what you are doing.
Complete Example
class MyApplicationBindings extends ApplicationBindings {
@override
List<Bind<Object>> bindings() => [
Bind.singleton(
(i) async => SharedPreferences.getInstance(),
),
Bind.singletonAsync(
(i) async => SharedPreferences.getInstance(),
),
Bind.lazySingleton(
(i) async => AsyncTest(),
),
Bind.lazySingletonAsync(
(i) async => Future.delayed(
const Duration(seconds: 4),
() => AsyncTest(),
),
),
Bind.factory(
(i) => MyDeepLink(),
),
Bind.factoryAsync(
(i) => Future.delayed(
const Duration(seconds: 4),
() => MyDeepLink(),
),
),
];
}
Important Information About Async Binds
- Through the Injector. shortcut, you can wait for your asynchronous Binds to be ready before starting any action. This is usually used during the transition from the SplashPage to load asynchronous dependencies.
Example:
class InitializeController with FlutterGetItMixin {
InitializeController();
@override
void dispose() {}
@override
void onInit() async {
await Injector.allReady();
// Do something
}
}
Important Information About Bind.factory and Bind.factoryAsync
- Each time you request an instance from FlutterGetIt, the factory will return a new instance of the requested object. However, you can define a
factoryTag
when instantiating the object. With this, FlutterGetIt will assign this tag to the object, making it unique in the tree. This way, when you request the object using the same tag, FlutterGetIt will return the already created instance, avoiding duplications and allowing the object to be called in other locations with the precision of the tag. You can also remove a Bind based on itsfactoryTag
.
Example of creation:
final fGetIt = context.get<FormItemController>(factoryTag: 'UniqueKeyString');
// Now this object is assigned to this tag, and in case of hotReload or request in another location, FlutterGetIt can identify it and prevent a new Object from being generated in the factory.
// Example in another controller.
final factoryCreatedInHomePage = context.get<FormItemController>(factoryTag: 'UniqueKeyString');
Example of removal:
Injector.unRegisterFactory<FormItemController>(UniqueKeyString);
// Here we will remove all objects of type T
Injector.unRegisterAllFactories<FormItemController>();
UI and Widgets
- FlutterGetIt provides a set of tools to assist in using components and rules in the UI.
FlutterGetItWidget
Attribute | Description |
---|---|
name |
The id is used for identifying the element internally and in the extension, for example, "/HomeWidget" or "WidgetCounter". |
binds |
Binds that will be instantiated and removed in the same lifecycle as the widget or page that uses them. |
onDispose |
Here you can execute an action before the Binds are closed and removed. |
onInit |
Here you can execute an action right after the Binds are initiated. |
builder |
Widget construction. |
Example:
FlutterGetItWidget(
name: id,
binds: [
Bind.factory(
(i) => FormItemController(
name: 'FormItemController',
),
),
],
onDispose: () {
Injector.unRegisterFactory<FormItemController>(id);
},
builder: (context) {
final fGetIt = context.get<FormItemController>(factoryTag: id);
return Column(
children: [
Text(fGetIt.name),
const SizedBox(height: 10),
TextField(
decoration: InputDecoration(
labelText: fGetIt.name,
border: const OutlineInputBorder(),
),
),
],
);
},
);
FlutterGetItView
- FlutterGetItView is a shortcut that extends StatelessWidget, allowing you to reduce the amount of code in the widget.
- Using it will provide a variable called
fGetit
that will take the value passed
Example:
class ActiveAccountPage extends FlutterGetItView<ActiveAccountController> {
const ActiveAccountPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(fGetIt.name),
),
body: ...,
);
}
}
Adding Lifecycle to Binds
FlutterGetItMixin - Object Lifecycle
- FlutterGetIt offers the FlutterGetItMixin mixin, which can be applied to your class, allowing the object to have a complete lifecycle, with initialization and finalization methods.
Attribute | Description |
---|---|
onDispose |
Called when the object is removed. |
onInit |
Called the first time the object is instantiated. |
Example:
class ActiveAccountController with FlutterGetItMixin {
final String name;
ActiveAccountController({required this.name});
@override
void onDispose() {}
@override
void onInit() {}
}
FlutterGetIt.navigator - Navigator 2.0
- FlutterGetIt supports Navigator 2.0, allowing you to instantiate modules and routes exclusively for the internal context. To do this, simply wrap the "Navigator" with FlutterGetIt.navigator.
Attribute | Description |
---|---|
navigatorName | Navigator name for identification in the tree and extension. |
bindings | Same rules used for main bindings, but must extend NavigatorBindings instead of ApplicationBindings. |
pages | Assignment of routes as in the "main". |
modules | Assignment of modules as in the "main". |
builder | Returns the context and routes as in the "main". |
Example:
Scaffold(
body: FlutterGetIt.navigator(
navigatorName: 'NAVbarProducts',
bindings: MyNavigatorBindings(),
modulesRouter: [
FlutterGetItModuleRouter(
name: '/Random',
bindings: [
Bind.lazySingleton<RandomController>(
(i) => RandomController('Random by FlutterGetItPageRouter'),
),
],
pages: [
FlutterGetItPageRouter(
name: '/Page',
page: (context) => const RandomPage(),
),
]
),
],
modules: [
HomeModule(),
DetailModule(),
AuthModule(),
],
builder: (context, routes) => Navigator(
key: internalNav,
initialRoute: '/Home/Page',
observers: const [],
onGenerateRoute: (settings) {
return PageRouteBuilder(
settings: settings,
pageBuilder: (context, animation, secondaryAnimation) =>
routes[settings.name]?.call(context) ?? const Placeholder(),
);
},
),
),
bottomNavigationBar: BottomNavigationBar...
...
...
Route Outlet
What is a RouteOutlet
The "Router Outlet" is a marker where the router inserts the component corresponding to the active route. This feature is extremely important in all applications as it allows you to add a component and load pages based on routes.
How to Use It
To use it, simply add the FlutterGetItRouteOutlet widget to the page where you want to have internal navigation. In it, you should configure the initial route (initialRoute) and the navigation key. With this, all navigation registered in the Material App will be loaded within the Router Outlet.
Here is a complete example with BottomNavBar:
Scaffold(
body: FlutterGetItRouteOutlet(
initialRoute: '/Auth/Login',
navKey: internalNav,
transitionsBuilder: (context, animation, secondaryAnimation, child) => ScaleTransition(
scale: Tween<double>(begin: 0.0, end: 1.0).animate(animation),
child: child,
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
if (_currentIndex != value) {
switch (value) {
case 0:
internalNav.currentState
?.pushNamedAndRemoveUntil('/Auth/Login', (_) => false);
case 1:
internalNav.currentState?.pushNamedAndRemoveUntil(
'/Auth/Register/Page', (_) => false);
case 2:
internalNav.currentState
?.pushNamedAndRemoveUntil('/RootNavBar/Root', (_) => false);
case 3:
internalNav.currentState
?.pushNamedAndRemoveUntil('/Landing/Initialize', (_) => false);
}
setState(() {
_currentIndex = value;
});
}
},
items: const [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
color: Colors.blueAccent,
),
label: 'Login',
),
BottomNavigationBarItem(
icon: Icon(
Icons.production_quantity_limits,
color: Colors.blueAccent,
),
label: 'Register',
),
BottomNavigationBarItem(
icon: Icon(
Icons.home,
color: Colors.blueAccent,
),
label: 'Custom NavBar',
),
BottomNavigationBarItem(
icon: Icon(
Icons.home,
color: Colors.blueAccent,
),
label: 'Presentation',
),
],
),
);
Customizing the Transition Animation
You can also customize the transition animation by adding the transitionsBuilder attribute.
transitionsBuilder: (context, animation, secondaryAnimation, child) => ScaleTransition(
scale: Tween<double>(begin: 0.0, end: 1.0).animate(animation),
child: child,
),