Dependency Injection in Flutter App Development

Utilizing Dependency Injection in Flutter

Implementing Inversion of Control uses the design pattern known as Dependency Injection (DI). It permits the development of dependent objects outside of a class and provides those objects in various ways to a class. The creation and binding of the dependent things are moved outside the class that depends on them using DI. This results in more flexibility, decoupling, and simpler testing.

In this article, we’ll look at a few simple instances of dependency injection in Flutter.

What would be the purpose of using DI?

I’m not going to get into the logic behind the pattern in detail, but think about the class that follows:

Code:

class CoffeeMachine {
   private Grinder grinder = new Grinder();
   
   makeCoffee() {
      grinder.grindCoffeeBeans();
      // Other steps to make coffee
   }
}

The previous class has no fundamental flaws, but it does have two significant issues:

It is impossible to separate and test the CoffeeMachine class using a dummy Grinder.
It would not be able to switch between a Manual Grinder and an Electric Grinder without changing the consumer.
We can fix these problems easily by creating different instances of CoffeeMachine with different types of grinders, as follows:

class CoffeeMachine {
  // Instance of the Grinder class
  final Grinder _grinder;

  // Constructor for dependency injection
  CoffeeMachine(this._grinder);

  // Method to make coffee
  makeCoffee() {
    _grinder.grindCoffeeBeans();
    // Other steps to make coffee
  }
}

Following the first approach clarifies why a class with many dependents may quickly get out of control.

Flutter’s Dependency Injection

Since Dependency Injection is a simple paradigm, libraries frequently abstract it from developers. These Flutter libraries often use reflection (mirrors in Dart). But there are two problems with Flutter:

For performance-related reasons, mirrors are disabled.
It is impractical to send dependencies many layers down the tree due to the nested nature of widgets.

We’ll use the Inject library to solve these problems. Inject uses the following annotations:

@Injector: A builder or collection of modules is used to create an injector, an inversion of a control container.
@Module and @Provides: Define classes and methods that provide dependencies to your Flutter app development.
@Component: It is used for dependency injection to enable specific modules.

Installation

Since there is no package in the official repository, we must manually install it. As a git package is how I prefer to approach it, I’ll describe its dependencies in the pubspec.yaml file as follows:

Code:

dependencies:
 inject:
 git:
 url: https://github.com/google/inject.dart.git
 path: package/inject
dev_dependencies:
 build_runner: ^1.3.0
inject_generator:
 git:
 url: https://github.com/google/inject.dart.git
 path: package/inject_generator

Usage

What features do we typically expect from a DI library? Let’s go over a few typical use cases:

Injection of class concrete

It may be as easy as this:

import 'package:inject/inject.dart';

@provide
class TaskService {
   // implementation
}

We can use it e.g. with Flutter widgets like this:

@provide
class SomeWidget extends StatelessWidget {
   final TaskService _service;
   SomeWidget(this._service);
}

Interface Injection

In the beginning, we must define an abstract class with an implementation class, such as:

// Abstract class for CustomerRepository
abstract class CustomerRepository {
  Future<List> allUsers();
}

// Implementation of CustomerRepository using Firestore
class FirestoreCustomerRepository implements CustomerRepository {
  @override
  Future<List> allUsers() {
    // Implementation for fetching all users from Firestore
  }
}

Now that we have dependencies, we can add them to our module:

import ‘package:inject/inject.dart’;
@module
class UsersServices {
  @provide
  CustomerRepository customerRepository() => FirestoreCustomerRepository();
}

Schedule Interview With Flutter Developers

Providers

What should we do if we require a provider to continually give us a new class instance rather than an instance of that class to be injected? Or what if having a concrete example in the constructor is preferable to lazily resolving the dependency? The fact that you may request a function returning the required instance, and it will be injected appropriately, is something I didn’t find in the documentation (well, because there isn’t any documentation at all).

Even so, we can define a helper type as follows:

typedef Provider = T Function();

You can also utilize it in classes:

@provide
class SomeWidget extends StatelessWidget {
   final Provider _service;
SomeWidget(this._service);
void _someFunction() {
   final service = _service();
   // use service
   }
}

Assisted injection

Since there is no built-in power to inject objects that need only runtime arguments, we can use the typical factory pattern in this situation: create a factory class that takes all the compile-time dependencies and injects them, and then provide a factory method with a runtime argument to make the necessary instance.

Qualifiers, singletons, and asynchronous injection

The library does support all of this. The official example provides a helpful explanation.

Installing it up

The last step is to build an injector (also known as a Dagger component), for example, as follows:

import ‘main.inject.dart’ as g;
@Injector(const [UsersServices, DateResultsServices])
abstract class Main {
   @provide
   MyApp get app;
   static Future

create( UsersServices usersModule, DateResultsServices dateResultsModule, ) async { return await g.Main$Injector.create( usersModule, dateResultsModule, ); } }
Here, MyApp is the core widget of our application; UserService and DateResultsServices are previously specified modules, and main.inject. Dart is an automatically generated file (more on this later).

We can now describe our primary function as follows:

void main() async {
   var container = await Main.create(
      UsersServices(),
      DateResultsServices(),
   );
   runApp(container.app);
}

Running

We must utilize build runner to generate the necessary code because inject uses code generation. Use this command to:
Code:

flutter packages pub run build_runner build

However, one crucial point is to note: by default, the code is generated into the cache folder, which Flutter does not support (though a solution to this issue is being worked on). Therefore, we must include the file inject_generator.build.yaml with the following information:

builders:
 inject_generator:
 target: “:inject_generator”
 import: “package:inject_generator/inject_generator.dart”
 builder_factories:
 — “summarizeBuilder”
 — “generateBuilder”
 build_extensions:
 “.dart”:
 — “.inject.summary”
 — “.inject.dart”
 auto_apply: dependents
 build_to: source

Wrap up

Dependency injection (DI) is a powerful design pattern that can help you write better Flutter code. It promotes modularity, reusability, testability, and maintainability by decoupling your code from its dependencies.

DI can be complex to implement initially, but many resources are available to help you, including DI libraries such as GetIt and Provider. If you are new to DI or need assistance implementing it in your Flutter project, you may hire flutter expert from a leading Flutter app development company who is always ready to assist you.

Frequently Asked Questions (FAQs)

1. What is dependency injection in Flutter app development?

Dependency injection in Flutter is a design pattern that allows you to decouple your code by injecting dependencies into classes instead of creating them directly.

2. What is dependency injection in use?

Dependency injection is utilized when creating a loosely linked application or making a class independent of its dependencies. The reusability of code can be increased by using dependency injection. If an object’s usage is decoupled, more dependencies can be replaced without altering classes.

3. Why is dependency injection advantageous?

Decreased coupling between classes and their dependencies is a crucial advantage of dependency injection. Programs become more reusable, testable, and manageable by hiding the implementation details from clients.

Book Your Flutter Developer Now

Nirali Patel

Written by Nirali Patel

Nirali Patel is a dedicated Flutter developer with over two years of experience, specializing in creating seamless mobile applications using Dart. With a passion for crafting user-centric solutions, Nirali combines technical proficiency with innovative thinking to push the boundaries of mobile app development.

Leave a comment

Your email address will not be published. Required fields are marked *


Discuss Your Project

Connect with Flutter Agency's proficient skilled team for your app development projects across different technologies. We'd love to hear from you! Fill out the form below to discuss your project.

Have Project For Us

Get in Touch

"*" indicates required fields

ready to get started?

Fill out the form below and we will be in touch soon!

"*" indicates required fields