DEV Community

Mattia Pispisa
Mattia Pispisa

Posted on

Flutter Monorepo & Dependency Injection

I described dependency inversion and a way to use in dart/flutter in this article. In this post I will use the same concepts and libraries.

Monorepo

Monorepo refers to the practice of software development in which all components of an application or system are maintained within a single repository. A good article that describes well what it is, pros and cons is the following: monorepo.tools.
In the case of a flutter application an excellent monorepo manager is Melos, here a tutorial on how to create and manage it.

A concrete use case

UML

Suppose our application is a social app and we need to manage posts and users. The application is divided into packages where each package contains views and logics to manage a specific section of the application (users, posts, ...). Route is defined within the core package. At this point each package defines its routes which will then be displayed in the sidebar (see uml). The result is as follows:

  • The abstract class Route is in core;
  • Its implementations are in different packages (PostRoute, UserRoute, ...).

Implementation

This can be the folder structure,

app
  - lib
    ...
  - pubspec.yaml

modules
  - core
      - pubspec.yaml
      ...
  - user
      - pubspec.yaml
      ...
  - post
      - pubspec.yaml
      ...
Enter fullscreen mode Exit fullscreen mode

where the various pubspec.yaml will look like:

## app
dependencies:
  core: 
    path: ../modules/core
  user:
    path: ../modules/user
  post:
    path: ../modules/post
Enter fullscreen mode Exit fullscreen mode
## user
dependencies:
  core: 
    path: ../core
Enter fullscreen mode Exit fullscreen mode
// app code
@Injectable(as: Navigation)
class AppNavigation implements Navigation {
  @override
  List<Route> routes = [UsersRoute()];
}

// core code
@injectable  
class SideBarMenu extends StatelessWidget {
  const SideBarMenu(this.navigation);

  final Navigation navigation;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: navigation.routes
          .map(
            (r) => Link(
              url: r.url,
              child: Text(r.name),
            ),
          )
          .toList(),
    );
  }
}

abstract class Navigation {
  List<Route> get routes;
}

abstract class Route {
  String get url;
  String get name;
  Widget view();
}

// post package
class PostsRoute implements Route {
  String url => "posts";
  String name => "Posts";
  Widget view => Container();
}
Enter fullscreen mode Exit fullscreen mode

Each package defines a micropackage (core, user, posts). Additionally in core we define getIt and export it for the other modules.

// only in core
GetIt getIt = GetIt.instance;

// every package
@InjectableInit.microPackage()
initMicroPackage() {} 
Enter fullscreen mode Exit fullscreen mode

In app we init the injection and register the various micro packages.

@InjectableInit(
    initializerName: 'init',
    preferRelativeImports: true,
    asExtension: false, 
    // packages
    externalPackageModulesBefore: [
      ExternalModule(CorePackageModule),
      ExternalModule(UserPackageModule),
      ExternalModule(PostPackageModule),
    ])
FutureOr<void> configureInjection() => init();
Enter fullscreen mode Exit fullscreen mode

In this case all packages were registered (in order) with the method externalPackageModulesBefore but there are three properties that can be used (in order of registration): 1. externalPackageModulesBefore, 2. externalPackageModules, 3. externalPackageModulesAfter.

Conclusion

This concludes the article on Monorepo & Dependency Injection.

For more information on micropackage I recommend reading injectable package.

Top comments (0)