Modularity
LogoModularity

Modularity

Modular Architecture for Flutter

Strict dependency injection, deterministic lifecycle, and enterprise-grade module isolation. Works with any router and any state manager.

M Core
A Auth
P Pay
UUser
N Nav
SSession
DI lifecycle scope DAG

Highlights

The generated Jaspr site keeps the same polished docs shell, but reshapes it into a focused landing page.

🧩

Zero Initialization Hell

Modules initialize in dependency order via concurrent DAG resolution. onInit is guaranteed to run after all imports are ready - no race conditions, no manual ordering.

🔒

Strict DI Boundaries

Private binds stay private, public exports are sealed after registration. No hidden globals, no implicit access. Every dependency is explicit and type-safe.

🔄

Formal Lifecycle

State machine with clear transitions: initial, loading, loaded, disposed. Automatic disposal on route pop, configurable retention policies (routeBound, keepAlive, strict).

🛠️

Router & State Agnostic

Works with GoRouter, AutoRoute, Navigator 1.0. Integrates with Bloc, Riverpod, MobX, or none. Modularity handles DI and lifecycle - you pick the rest.

Install#

flutter pub add modularity_flutter modularity_core

Quick Start#

Each feature gets its own module with private implementation and public exports. Dependencies are explicit - no globals, no magic.

import 'package:modularity_core/modularity_core.dart';

class AuthModule extends Module {
  @override
  void binds(Binder i) {
    // Private - only this module can access
    i.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl());
    i.registerFactory<LoginUseCase>(
      () => LoginUseCase(i.get<AuthRepository>()),
    );
  }

  @override
  void exports(Binder i) {
    // Public - available to parent and sibling modules
    i.registerLazySingleton<AuthService>(
      () => AuthService(i.get<AuthRepository>()),
    );
  }
}

Wrap your app with ModularityRoot and place ModuleScope where you need modules. Loading and error states are handled automatically.

final observer = RouteObserver<ModalRoute<dynamic>>();

void main() {
  runApp(ModularityRoot(
    observer: observer,
    defaultLoadingBuilder: (_) =>
        const Center(child: CircularProgressIndicator()),
    child: MaterialApp(
      navigatorObservers: [observer],
      home: ModuleScope(
        module: AppModule(),
        child: const HomePage(),
      ),
    ),
  ));
}

Access dependencies from the nearest module scope. Type-safe, no string keys, no service locators.

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final auth = ModuleProvider.of(context).get<AuthService>();
    return Scaffold(
      body: Center(
        child: Text('Logged in: ${auth.isLoggedIn}'),
      ),
    );
  }
}

Test modules in pure Dart - no Flutter, no widget pumping. Verify registrations and resolutions directly.

import 'package:modularity_test/modularity_test.dart';

test('AuthModule registers expected deps', () async {
  await testModule(AuthModule(), (module, binder) {
    expect(binder.hasSingleton<AuthRepository>(), isTrue);
    expect(binder.hasSingleton<AuthService>(), isTrue);

    final repo = binder.get<AuthRepository>();
    expect(repo, isA<AuthRepositoryImpl>());
  });
});

Packages#

Packagepub.devDescription
modularity_contracts modularity_contracts Zero-dependency interfaces: Module, Binder, exceptions
modularity_core modularity_core DI container, lifecycle state machine, DAG resolver
modularity_flutter modularity_flutter Flutter widgets: ModularityRoot, ModuleScope, ModuleProvider
modularity_test modularity_test Unit test helpers: testModule, TestBinder
modularity_cli modularity_cli Graphviz and interactive dependency graph visualization
modularity_get_it modularity_get_it GetIt adapter for migration from existing GetIt projects

Why Modularity?#

Most Flutter DI solutions give you a container but leave initialization order, scope boundaries, and lifecycle management to you. As apps grow, this leads to:

  • Initialization hell - services depend on other services that aren't ready yet
  • Hidden globals - anything can access anything, making refactoring risky
  • No formal lifecycle - disposal is manual and error-prone
  • Router lock-in - DI is tightly coupled to a specific navigation library

Modularity solves all four with a formal module system inspired by NestJS and Angular, adapted for Flutter's widget tree.

How It Compares#

FeatureModularityflutter_modularProvider / Riverpod
Module definition Pure Dart class + state machine Class with routes (router-coupled) No formal module concept
Initialization Concurrent DAG, deterministic Lazy or navigation-based Lazy or widget-mount
Visibility Explicit binds/exports, sealed Implicit parent access Global or scoped
LifecycleFormal state machineBound to routerBound to widget tree
Router coupling None - works with any router Strong - router is core Indirect
Unit testing Pure Dart, no Flutter needed Integration-focused Requires pumpWidget

Advanced Features#

Configurable Modules#

Pass runtime arguments to modules - perfect for detail screens, feature flags, or environment config.

class ProductModule extends Module implements Configurable<String> {
  late String productId;

  @override
  void configure(String args) => productId = args;
}

// In your route:
ModuleScope(module: ProductModule(), args: productId, child: ...)

Hierarchical Overrides#

Swap dependencies for testing, feature flags, or A/B experiments without changing module code.

final overrides = ModuleOverrideScope(
  children: {
    AuthModule: ModuleOverrideScope(
      selfOverrides: (binder) {
        binder.registerLazySingleton<AuthService>(() => MockAuthService());
      },
    ),
  },
);

ModuleScope(module: AppModule(), overrideScope: overrides, child: ...)

Dependency Graph Visualization#

See your entire module hierarchy as an interactive diagram. Find circular dependencies and hidden coupling before they become problems.

import 'package:modularity_cli/modularity_cli.dart';

void main() async {
  await GraphVisualizer.visualize(AppModule(), renderer: GraphRenderer.g6);
}

Learn More#

  • Guide - Architecture overview and recipes
  • API Reference - Full API documentation for all packages
  • GitHub - Source code and issues