arch-atlas

Get it part 1

Presentation (Integration)

05:24

Summary

This lesson demonstrates the practical problems that arise when manually instantiating complex dependency trees in the UI layer. By attempting to provide use cases to multiple Cubits, the instructor exposes how inline instantiation creates deeply nested, repetitive code. The lesson highlights two major architectural flaws with this approach: tight coupling that makes refactoring difficult and the inefficient creation of redundant object instances. This sets the stage for introducing Dependency Injection as the required solution.

Key Ideas

  • Abstract classes cannot be instantiated directly; their concrete implementations must be provided in the dependency tree.
  • Constructing dependency trees inline leads to deeply nested and unreadable boilerplate in the presentation layer.
  • Inline instantiation creates tight coupling, where adding a single new parameter to a low-level service breaks the compilation across the entire UI layer.
  • Manually creating dependencies for every Bloc/Cubit results in redundant memory allocations for objects that could be shared.
  • Dependency Injection is introduced as the necessary design pattern to decouple object creation from object usage.

Code Snippets

The problematic inline dependency instantiation for multiple Cubitsdart
MultiBlocProvider(
  providers: [
    BlocProvider(
      create: (context) => FeaturedBooksCubit(
        FetchFeaturedBooksUseCase(
          HomeRepoImpl(
            homeLocalDataSource: HomeLocalDataSourceImpl(),
            homeRemoteDataSource: HomeRemoteDataSourceImpl(
              ApiService(
                Dio(),
              ),
            ),
          ),
        ),
      ),
    ),
    BlocProvider(
      create: (context) => NewestBooksCubit(
        FetchNewestBooksUseCase(
          HomeRepoImpl(
            homeLocalDataSource: HomeLocalDataSourceImpl(),
            homeRemoteDataSource: HomeRemoteDataSourceImpl(
              ApiService(
                Dio(),
              ),
            ),
          ),
        ),
      ),
    ),
  ],
  // ... child widget
)
Adding a new parameter to a low-level service breaks all inline instantiationsdart
class ApiService {
  final Dio _dio;
  final int x;

  final baseUrl = "https://www.googleapis.com/books/v1/";

  ApiService(this._dio, this.x);

  // ... get method
}

Architecture Insight

Architecture insight

GetIt is a service locator — a global registry the rest of the app reads from instead of "new"-ing dependencies inline. It's the pragmatic Flutter answer to "how does the Cubit get its use case, which gets its repo, which gets its data sources?"

The CA-strict alternative is constructor injection all the way down: pass the use case to the widget, which passes it to the Cubit, which passes it to... and you give up after three layers. GetIt collapses that ceremony into one registration up front.

The trade is that the dependencies become invisible at the call site — a Cubit that does getIt<FetchFeaturedBooksUseCase>() looks self-contained but actually depends on global state.

My Notes

The instructor does an excellent job of visually demonstrating why we need Dependency Injection before just introducing the get_it package. Showing the compiler errors cascade through the presentation layer when a single parameter is added to ApiService is a very effective teaching technique.

  • [Editor's note] While the instructor points out the inefficiency of creating multiple ApiService and Dio instances, it's worth emphasizing that Dio is specifically designed to be reused as a singleton. Creating multiple Dio instances ruins connection pooling and makes global configurations (like auth interceptors or logging) nearly impossible to manage correctly. The transition to a Service Locator (get_it) in the next lesson is the standard Flutter solution for this.