• Non ci sono risultati.

4.3 Software Engineering patterns

4.3.2 Dependency Injection

All object-oriented applications are based on a set of classes which interact with each other according to some logic: the way an object is linked with other ones builds a set of dependencies; for example, in the last section the dependency between Data Layer’s

sub-domains’ object and the dependency between Repositories and Blocs (or Cubits) has been highlighted with some code snippets. When the code base becomes larger, it might be stressful and cumbersome to deal with all the dependencies; indeed, this was the case of BlocP rovider objects which follow a strategy that suits mostly cases where few page navigation actions can happen (see section 4.3.1.1): thus, it would be great to retrieve the correct Bloc (or Cubit) directly in the UI code without worrying about widget tree mechanisms. So the point of dependency injection is to reduce the static coupling between objects in object-oriented languages, without compromising code quality and maintainability (otherwise it would be enough to write code in just one file). Moreover, since dependencies cannot disappear and they are a solid part of the application structure, the idea behind this software engineering pattern is to move the scope of the dependency from the object to its type: the best way to reach this goal is changing the attribute reference type from the actual class to the interface type that the class implements.

The following code snippet shows an application of these concepts to the dependency between the LatestM easureCubit class and the M easureRepository class: in fact, when the Cubit needs to be constructed, a third-party entity is responsible for passing a M easureRepository object that implements the M easureRepositoryInterf ace. Notice that the LatestM easureCubit doesn’t need to know how a M easureRepository object is constructed because someone will provide an already built copy of it; further, this is the mechanism that stands behind Bloc’s (or Cubit’s) unit tests and that allows to substitute the real Repository object with a mock one for testing purposes.

Dependency injection derives from a wider technique, called Inversion of Control (IoC):

this technique, indeed, consists in substituting the older principle stating that each object directly chooses objects on which it depends with the principle based on the fact that someone else will be providing the objects on which its depends.

class LatestMeasureCubit extends Cubit<LatestMeasureState> { LatestMeasureCubit({@required this.measureRepository})

: super(DaysMeasureOngoing());

MeasureRepositoryInterface measureRepository;

...

}

How does this work? How are objects injected where needed? The f lutter modular package does all the magic work. The application’s functionalities will be represented by decoupled and independent modules in a M odular application: each module has its own dependencies, routes, widgets and business logic, and it is housed in its own directory; as a result, it becomes straightforward to quickly remove a module from the project and use it elsewhere.

Every mobile application using this package must have a M ainM odule, that is the module that makes the application start. The following code reports the main() function (that is also the entrypoint) of the application and part of the AppM odule class where a list of dependencies to be injected is provided, together with the root widget associated to the main module (usually it is the M aterialApp widget). In this code snippet, the Bind object is responsible for configuring the object injection inside the module: thus, the binds getter is returning the list of objects that can be accessed at any time provided that this module is not out of scope; indeed, a module becomes out of scope when none of its routes (see next section) is the current route represented on the screen.

Study and development of a participative system for monitoring air pollution data

class AppModule extends MainModule {

@override

List<Bind> get binds => [

Bind((i) => UserRepository()), Bind((i) => MeasureRepository()),

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

Bind((i) => AuthenticationBloc(userRepository: i.get<UserRepository>()), lazy: false),

Bind((i) => InternetCubit(connectivity: Connectivity()), lazy: false), Bind((i) => BoardConnectionBloc(

databaseRepository: i.get<DatabaseRepository>(), internetCubit: i.get<InternetCubit>(),

measureRepository: i.get<MeasureRepository>()), lazy: false),

Bind((i) => NotificationsHandler(), lazy: false), Bind((i) => RoutingRepository()),

];

...

@override

Widget get bootstrap => PolitoWeatherStationApp();

}

Future<void> main() async { ...

runApp(EasyLocalization(

... ,

child: ModularApp(module: AppModule())));

}

In order to retrieve one of the objects that can be inject inside module’s code, it’s enough to use the static method M odular.get < ClassN ame > (): in this way, as the code below shows, it is possible to use the reference to that single object belonging to the ClassN ame class. Actually, there is no mechanism to solve ambiguities, so it is recom-mended to instantiate a class only once inside the binds list getter. Though, despite the natural behaviour of this software engineering pattern requires to use singletones (classes to be instantiated only once), it is possible to create a new instance of the ClassN ame class every time the M odular.get < ClassN ame > () method is invoked by passing the f alse value to the Bind constructor’s singleton parameter. Moreover, if the developer wants to instantiate the class ClassN ame as soon as possible and not after the first in-vocation of the M odular.get < ClassN ame > () method, it is possible to set the lazy parameter of the Bind constructor to f alse. Otherwise, the default behaviour will in-stantiate the class after the first request and it will always retrieve this same object every time when required to.

The following code snippet shows a use case of the M odular.get < ClassN ame > () method: here, the dependency injection is exploited to avoid those problems, mentioned in section 4.3.1.1, related to the BlocP rovider widget.

BlocBuilder(

cubit: Modular.get<LatestMeasureCubit>(),

builder: (BuildContext context, LatestMeasureState state) { if (state is DaysMeasureOngoing) {

...

} else if (state is DaysMeasureAvailable) { ...

} } )

Another interesting implementation of M odular’s dependency injection mechanism is achieved by the M odularState. This abstract class can be used in a stateful widget when

there is the need to retrieve a controller only in the current Page - that is a stateful widget covering the whole navigation screen. Indeed, considering the example below taken from the BodyState class of the Home page, by using this trick the variable controller is automatically instantiated as an injection of the ChipsController class, even though this class was not listed in the binds list of the module. In particular, M odularState will destroy the controller variable as soon as the page is destroyed, that is when a new route is requested; this mechanism is very interesting because it binds view and controller as a single component.

class _BodyState extends ModularState<Body, ChipsController> ... { ...

Widget build(BuildContext context) { ...

Column(children: [

ChipsList(controller: controller), ...

]) ...

} }

Finally, this examples introduces the last realization of dependency injection, based on ChangeN otif ier and Consumer classes. As shown in the following code snippet, the ChipsController class extends ChangeN otif ier, which means that, by calling the notif yListeners() method this class can trigger the rebuild of a part of the UI according to the changes occurred before the call. Which part of the UI? The sub-tree that has a Consumer widget as the root node of the widget sub-tree, as displayed in the code snippet.

This mechanism has been strategically used in this scenario, because the ChipsController class - that internally knows which of the chips in the upper part of the Home page (see figure 4.2 on page 57) has been selected - can trigger not only the border color of the chip itself but also the UI of the entire page since most of the widgets depend on which day is currently selected.

class ChipsController extends ChangeNotifier { int selected = 5;

Map<String, Color> chipsData = {};

...

int setSelected(int index) { this.selected = index;

notifyListeners();

} }

class CustomChip extends StatelessWidget { final int index;

...

CustomChip({Key key, this.label, this.index, this.color, this.selected}) : super(key: key);

Widget build(BuildContext context) { return Consumer<ChipsController>(

builder: (context, chipsController) => AnimatedContainer(

margin: const EdgeInsets.only(left: 9.0), width: 100,

Study and development of a participative system for monitoring air pollution data

height: 30,

duration: const Duration(milliseconds: 300), decoration: BoxDecoration(

...

border: Border.all(

color: index == chipsController.selected

? darkTextColor : Colors.grey,

width: index == chipsController.selected ? 2 : 1, ),

),

child: InkWell(

onTap: () async => {chipsController.setSelected(index)}, child: ...

)), );

} }