Codenic Clean Architecture
  • Introduction
  • The Clean Architecture
    • Presentation Layer
    • Domain Layer
    • Infrastructure Layer
  • Tutorial
    • Overview
    • Creating a Flutter Modular Project
      • Tips for Managing a Modular Project
    • Implementing the Domain Layer
      • Core Dependencies
      • Creating an Entity
      • Creating a Failure
      • Creating a Repository Interface
      • Creating Use Cases
        • CRUD Operations (Runner)
        • Data Streams (Watcher)
    • Implementing the Infrastructure Layer
      • External Dependencies
      • Creating a Data Model
      • Creating a Data Source
      • Implementing a Repository
    • Implementing the Presentation Layer
      • External Dependencies
      • Dependency Injection and Service Locator
      • Widgets
        • Snackbar Handler
        • Global Blocs Widget
        • Note Widgets
  • Packages
    • Codenic Bloc Use Case
      • Runner
      • Watcher
    • Codenic Logger
      • Usage
      • Example
      • Modifying the Logger
      • Integrating Firebase Crashlytics to the logger
    • Codenic Exception Converter
      • Failure Object
      • Exception Converter
      • Exception Converter Suite
      • Example
Powered by GitBook
On this page
  • 1. Create the service locator instance
  • 2. Create the service locator initializer
  • 3. Initialize the service locator and dependencies
  1. Tutorial
  2. Implementing the Presentation Layer

Dependency Injection and Service Locator

PreviousExternal DependenciesNextWidgets

Last updated 2 years ago

Dependency injection is a design pattern that allows a class to receive its dependencies from external sources rather than creating them internally. This decouples the class from its dependencies, making the code easier to test, maintain, and evolve. The most common way to inject dependencies is through constructor injection, where the dependencies are passed to the class's constructor when the object is created.

Service locator, on the other hand, is a design pattern that allows a class to look up dependencies in a central registry, either as a class factory or a singleton.

In this part of the tutorial, we're going to cover the following:

  • Combine both design patterns by using a service locator to manage the creation and storage of class factories and singletons, as well as to inject dependencies into these classes.

  • Setup the app entry point to initialize the service locator and dependencies.

1. Create the service locator instance

The service locator is primarily used by the Presentation Layer to call So, in the note_app/modules/presentation/lib/presentation.dart barrel file, let's set a serviceLocator variable to the default GetIt.instance:

import 'package:get_it/get_it.dart';

final serviceLocator = GetIt.instance;

Once done, fetch the dependencies so that the main module can access the serviceLocator:

flutter pub get modules/presentation

2. Create the service locator initializer

In the note_app/lib/ directory, create a file named injectables.dart and create a function called initializeServiceLocator. This will be used to inject the external dependencies, data sources, repositories and use cases using the serviceLocator previously created.

In this example, all external dependencies, and will be registered as a Lazy Singleton, whereas all will be registered as a Class Factory.

/// Initializes the service locator by registering all external dependencies, 
/// data sources, repositories and use cases.
void initializeServiceLocator() {
  serviceLocator.registerLazySingleton<ExceptionConverterSuite>(
    () => ExceptionConverterSuite(),
  );

  serviceLocator
      .registerLazySingleton<SqlbriteDataSource>(() => SqlbriteDataSource());

  // When injecting repositories, make sure to use the interface as the generic
  // type (i.e. NoteRepository instead of NoteRepositoryImpl) so that the
  // repository can be swapped out with a different implementation
  serviceLocator.registerLazySingleton<NoteRepository>(
    () => NoteRepositoryImpl(
      exceptionConverterSuite: serviceLocator(),
      sqlbriteDataSource: serviceLocator(),
    ),
  );

  // Inject use cases as factories so that they can be instantiated more than
  // once

  serviceLocator.registerFactory<CreateNoteEntry>(
    () => CreateNoteEntry(noteRepository: serviceLocator()),
  );

  serviceLocator.registerFactory<UpdateNoteEntry>(
    () => UpdateNoteEntry(noteRepository: serviceLocator()),
  );

  serviceLocator.registerFactory<DeleteNoteEntry>(
    () => DeleteNoteEntry(noteRepository: serviceLocator()),
  );

  serviceLocator.registerFactory<FetchNoteEntries>(
    () => FetchNoteEntries(noteRepository: serviceLocator()),
  );

  serviceLocator.registerFactory<WatchNoteEntries>(
    () => WatchNoteEntries(noteRepository: serviceLocator()),
  );
}

3. Initialize the service locator and dependencies

In the note_app/lib/main.dart file, initialize the service locator and all dependencies that require initialization:

import 'package:flutter/material.dart';
import 'package:infrastructure/data_sources/sqlbrite/sqlbrite_data_source.dart';
import 'package:note_app/injectables.dart';
import 'package:presentation/main_app.dart';
import 'package:presentation/presentation.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  initializeServiceLocator();

  // Initialize the [SqlbriteDataSource] before `runApp` is called to setup
  // the database
  await serviceLocator<SqlbriteDataSource>().initialize();

  // TODO: This will be created later
  runApp(const MainApp());
}

The will be created at a later part of the tutorial.

MainApp widget
Data Sources
Repositories
Use Cases.
Use Cases