module_provider 2.4.0 module_provider: ^2.4.0 copied to clipboard
Package to make it easy to build applications in the module structure, with service injection, components and state management with providers.
Module Provider 🚀 #
Create great applications, organizing your code structure in modules, with dependency injection (services), route management and state control.
Introduction #
The Module Provider is a package that facilitates the creation of simple and complex applications, using a concept of modules with dependency injection, similar to that used by Angular, having in the module the declaration of services (dependency injection) and routes navigation.
This package also provides classes for state managers, which facilitate the control of data changes and update the dependents (widgets) in each change
How to install #
Add the dependency on pubspec.yaml
.
Informing ^
at the beginning of the version, you will receive all updates that are made from version 2.0.0
up to the version before 3.0.0
.
dependencies:
module_provider: ^2.0.0
Import the package in the source code.
import 'package:module_provider/module_provider.dart';
Modules Structure #
A module is the main structure of the application, where all services used and navigation routes will be declared.
The use of routes is important for applications that have more than one module or have the need to navigate to other screens, this practice also facilitates the reading of the code, because as the routes are declared in the module, it is possible to identify all the screens that the module will open in just one location. If your application does not use navigation, using routes is optional.
The implementation of modules is standard throughout the system, with the exception of the first application module, which will be the Root Module
, and all other modules will be Child Modules
.
Below, it will be explained how the declaration of each type of module is made.
Root Module
As already explained above, the Root Module
is the main module of the application, the first to be created, which will contain all the sub-modules (Child Modules
), Component and any other type of Widget as descending, in this module, the implementation of the build
method will be required.
On main.dart
, I informed that the AppModule
is my root module, how much the module is create, the widget informed in build
method is returned. As this is the root module, the MaterialApp
should be returned.
import 'package:flutter/material.dart';
import 'package:app_module.dart';
void main() => runApp(AppModule());
In app_module.dart
, I created my module structure, with Services
and Routes
.
The Widget
created by the build
method will be MaterialApp
, and as the module has configured routes, the Module.onGenerateRoute
method must be passed to MaterialApp.onGenerateRoute
, so that the route manager can be used of the module.
import 'package:flutter/material.dart';
class AppModule extends Module {
@override
List<Inject<Service>> get services => [
(module) => AppService(module),
(module) => DataService(module),
];
@override
List<ModuleRoutePattern> get routes => [
ModuleRoute('', (context) => HomePage()),
ModuleRoute('page01', (context) => FirstPage()),
ModuleRoute('page02', (context) => SecondPage()),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Module Provider',
onGenerateRoute: Module.onGenerateRoute,
);
}
}
If you do not use routes, the Module.onGenerateRoute
method does not need to be used, and the main component will be informed directly in the MaterialApp.home
parameter.
import 'package:flutter/material.dart';
class AppModule extends Module {
@override
List<Inject<Service>> get services => [
(module) => AppService(module),
(module) => DataService(module),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Module Provider',
home: HomePage(),
);
}
}
Childs Module
The child modules are similar to the root modules, however the build
method will only be mandatory if no routes are used, or if the root route (/) is not defined.
In the example below, a child module that does not use routes will be created.
import 'package:flutter/material.dart';
class ChildModule extends Module {
@override
List<Inject<Service>> get services => [
(m, arg) => ChildService(m),
];
@override
Widget build(BuildContext context) {
return ChildPage();
}
}
The example below will show how a module that uses routes will look.
class ChildModule extends Module {
@override
List<Inject<Service>> get services => [
(m, arg) => ChildService(m),
];
@override
List<ModuleRoutePattern> get routes => [
ModuleRoute('', (context) => ChildPage()),
ModuleRoute('childpage01', (context) => FirstChildPage()),
ModuleRoute('childpage02', (context) => SecondChildPage()),
];
}
Service #
The Service
provide functions and properties to components, submodules and other services, he is created and maintained in memory until module be disposed.
In app_service.dart
, I created the AppService
and declared the darkMode
property of type bool
that defines whether the app will be built in dark mode or not.
class AppService extends Service {
bool _darkMode = false;
bool get darkMode => _darkMode;
AppService(Module module) : super(module);
}
Component #
The Components
are extended StatefulWidget
widgets, but simpler, it is not necessary to create a StatefulWidget
and State
class, and usually have an associated Controller
to maintain the state of the component.
Basic example of implementing a component.
class HomeComponent extends Component {
@override
Widget build(BuildContext context, Controller controller) {
return Scaffold(
body: Center(
child: Text('Home Component')
)
);
}
}
Implementation of a component with a custom controller.
class HomeComponent extends Component<HomeController> {
@override
initController(BuildContext context, Module module) => HomeController(module);
@override
Widget build(BuildContext context, HomeController controller) {
return Scaffold(
body: Center(
child: Text(controller.bodyMassage)
)
);
}
}
Controller #
The Controller
is used together a Component
to keep the logic and state separate from the component, leaving component solely responsible for the layout.
class HomeController extends Controller {
String bodyMassage = 'Home Componente';
HomeController(Module module) : super(module);
}
State Management #
State managers are classes that facilitate the control of change transfers, all of which have a consumer so that they can change a screen with each value change.
ValueProvider / ValueConsumer #
Simple class to notify listeners when value is changed.
ValueProvider<String> description = ValueProvider(initialValue: 'Initial Description');
You can modify value setting property 'value' or calling method 'setValue'.
description.value = 'Another Description';
description.setValue('Another Description');
Example to consume this value. In this case, when changing the description value, the Text Widget is rebuilt and shows the new value.
ValueConsumer<String>(
provider: description,
builder: (context, value) => Text(value)
);
ValuesProvider / ValuesConsumer #
This class is similar to ValueProvider
, but allows the control of several values using a Map
.
In the bellow example , is declared 'packageInfo' with the values 'name' os type String and value 'version' of type ValueProvider
ValuesProvider packageInfo = ValuesProvider({
'name': 'package_name',
'version': ValueProvider<String>(initialValue: ''),
});
To update the values on ValuesProvider use setValues
method passing a Map<String, dynamic>
and to update a single value, use setValue
with fieldName
and newValue
arguments.
packageInfo.setValues({
'name': 'module_provider',
'version': '1.0.0'
});
Example for consume all values of the ValuesProvider.
ValuesConsumer(
provider: packageInfo,
builder: (context, values) => Column() {
childs: Widget[] {
Text(values['name']),
Text(values['version'])
}
}
);
Example for consume a ValueProvider in the ValuesProvider.
ValueConsumer<String>(
provider: packageInfo.getValueProvider('version'),
builder: (context, value) => Text(value)
);
ListProvider / ListConsumer #
Notify listeners based on changing a list of objects.
This class that follows the same principle of ValueProvier
and ValuesProvider
but with inheritance of ListMixin
, with this you will be able to perform the same operations as a standard List
, and with each modification of the list the listeners will be notified.
ListProvider<String> movies = ValueProvider(initialValue: [
'Star Wars',
'Terminator 2: Judgment Day'
]);
As explained above, you can use the same methods as a standard List
, below is a basic usage example.
movies.add('Total Recall');
movies.addAll([
'Matrix',
'Tron: Legacy'
]);
Example to consume this list. In this case, when changing the list, the Text Widget is rebuilt and shows the new value.
return ListConsumer<String>(
list: movies,
builder: (context, movies) {
return ListView.separated(
itemCount: movies.length,
itemBuilder: (context, index) => Text(movies[index]),
separatorBuilder: (context, index) => Divider()
);
}
);