Search⌘ K
AI Features

Introduction to Dependency Injection

Explore the concept of dependency injection and understand its benefits such as decoupling and testability. Learn how to implement dependency injection in Flutter using constructors and discover how GetX simplifies this process by providing easier access to shared dependencies across widget trees for more scalable and maintainable applications.

Overview

Dependency injection is a design pattern that involves supplying dependencies (such as objects, services, or configurations) to a class or function from an external source, rather than having the class create or manage its dependencies internally. It brings the following benefits to the table:

  • Decoupling and modularity: Dependency injection promotes loose coupling between the presentation and business logic layers by separating the creation and management of dependencies from the core logic of classes. This improves modularity and makes the codebase easier to maintain and extend.

  • Testability: It simplifies unit testing by allowing dependencies to be replaced with mock or test implementations. This facilitates isolated testing of components without relying on complex setups or real external services.

  • Code reusability: This technique allows dependencies to be reused across different classes or modules, reducing code duplication and promoting a more modular and scalable architecture.

  • Flexibility and configurability: Dependency injection allows for flexibility in configuring and swapping dependencies at runtime, enabling dynamic behavior and customization of application components.

Let’s look at the example of dependency injection:

Basic dependency injection

The basic method to inject dependencies into a class is using the constructor. We pass the desired dependencies in the class constructor and are good to go. Here’s how:

Dart
class First {
// Dependencies that are required from outside the class.
var dependency1;
var dependency2;
First(this.dependency1, this.dependency2);
}
class Second {
var one = 'ONE';
var two = 'TWO';
// Creating instance of [First] class and injecting dependencies.
var first = First(one, two);
}

The example above shows how we pass the dependencies from class Second to class First while creating the instance.

Next, let’s see how the same technique works in Flutter.

Dependency injection in Flutter

As Flutter widgets are nothing but classes, we again use constructors to pass dependencies around.

Dart
class Widget1 extends StatelessWidget {
final String name;
Widget1(this.name);
@override
Widget build(BuildContext context) {
return Text(name);
}
}
class Widget2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Widget2('Aachman');
);
}
}

So this is how we do basic dependency injection in Flutter. There’s no problem with this approach except for one—it’s not scalable.

We pass dependencies in the widget tree from top to bottom. If a widget way down in the bottom needs access to a specific variable, we must pass it through all the widgets in the tree. This can quickly get cumbersome. We need a better way to do it!

Dependency injection in GetX

GetX creates a shortcut path for dependencies. Instead of passing the dependency down through each widget, we initialize the dependency in one widget, and it gets accessible by all the widgets down the tree. Let’s see how it’s done!

  1. First, we create a controller that holds all our dependencies:

Dart
class Controller extends GetxController {
String name = 'Aachman';
}
  1. Next, we initialize the controller using GetX.

Dart
class Widget1 extends StatelessWidget {
final Controller controller = Get.put(Controller());
}

We use Get.put when we initialize the controller for the first time. This way, this instance of the controller becomes accessible by every widget down in the tree.

  1. To access the instance, we use Get.find.

Dart
class Widget2 extends StatelessWidget {
final Controller controller = Get.find();
@override
Widget build(BuildContext context) {
return Text(controller.name);
}
}

It’s the same instance as used in Widget1, which means that if we update the variable name’s value, it gets updated for both widgets. Now we understand the concept dependency injection and how we inject dependencies using GetX.