flutter_microfrontend 1.0.0
flutter_microfrontend: ^1.0.0 copied to clipboard
A powerful, production-ready Flutter package for building large-scale applications using micro-frontend architecture. Enables true module independence with lazy loading, dynamic module registration, s [...]
flutter_microfrontend #
Build large-scale Flutter apps as independent, lazy-loadable feature modules.
flutter_microfrontend brings micro-frontend architecture principles to Flutter β enabling teams to split apps into fully isolated feature units that can be loaded, unloaded, and developed independently.
β¨ Feature Highlights #
| Feature | Description |
|---|---|
| π True Module Isolation | Each module has its own DI container, state scope, and event subscriptions |
| β‘ Lazy Loading | 6 preload strategies β load modules only when needed |
| π‘ Typed Event Bus | RxDart-powered pub/sub with type safety, debounce, throttle |
| π Plugin Architecture | Analytics, Crash Reporting, Feature Flags β all decoupled |
| πΊοΈ Modular Routing | Auto-collects routes from all modules via GoRouter |
| π§ͺ Highly Testable | Every module is independently testable with mock helpers |
| π Dynamic Registration | Register/unregister modules at runtime |
| π‘οΈ Error Isolation | ModuleErrorBoundary prevents cascade failures |
| π Lifecycle Events | Observable stream of every module state change |
| π Dep Graph Analysis | Visualize dependencies, detect cycles at startup |
π¦ Installation #
dependencies:
flutter_microfrontend: ^1.0.0
flutter pub get
π Quick Start #
1. Create a Module #
import 'package:flutter_microfrontend/flutter_microfrontend.dart';
import 'package:go_router/go_router.dart';
class AuthModule extends MicroModule with RoutableModule, EventAwareModule {
@override String get moduleId => 'auth';
@override String get moduleName => 'Authentication';
@override bool get isEager => true;
@override int get loadPriority => 100;
@override List<Type> get publishedEvents => [UserSignedInEvent];
@override
Future<void> onRegister(ModuleInjector injector) async {
injector.registerLazySingleton<AuthService>(() => FirebaseAuthService());
}
@override
List<RouteBase> get routes => [
GoRoute(
path: '/login',
builder: (ctx, state) => LoginScreen(
onSuccess: (userId) => emit(UserSignedInEvent(
userId: userId, sourceModuleId: moduleId,
)),
),
),
];
}
2. Bootstrap the App #
void main() {
runApp(
MicrofrontendApp(
title: 'My App',
modules: [
// Eager β critical modules loaded at startup
AuthModule(),
HomeModule(),
// Lazy β loaded on demand
DeferredModule(
moduleId: 'shop',
loader: () async => ShopModule(),
preloadStrategy: PreloadStrategy.afterAppReady,
dependencies: ['auth'],
),
DeferredModule(
moduleId: 'settings',
loader: () async => SettingsModule(),
preloadStrategy: PreloadStrategy.whenIdle,
),
],
plugins: [
FirebaseAnalyticsPlugin(),
SentryPlugin(dsn: 'https://...'),
],
sharedServices: (injector) {
injector.registerSingleton<HttpClient>(DioClient());
injector.registerSingleton<LocalStorage>(HiveStorage());
},
),
);
}
π§© Module Contracts (Mixins) #
| Mixin | Purpose |
|---|---|
RoutableModule |
Declare GoRouter routes |
EventAwareModule |
Publish and subscribe to typed events |
ServiceModule |
Expose services to the global DI container |
class ShopModule extends MicroModule
with RoutableModule, EventAwareModule, ServiceModule {
@override String get moduleId => 'shop';
// ... implements all three contracts
}
π‘ Event System #
// Publisher (in ShopModule)
emit(OrderPlacedEvent(orderId: 'ORD-123', sourceModuleId: moduleId));
// Subscriber (in any other module, no coupling)
on<OrderPlacedEvent>().listen((event) {
showNotification('Order ${event.orderId} placed!');
});
// Auto-cancelled listener (tied to module lifecycle)
listen<UserSignedOutEvent>((event) => clearCart());
// Advanced: filter + debounce
eventBus.subscribe<SearchQueryEvent>(
onEvent: runSearch,
where: (e) => e.query.length > 2,
debounce: const Duration(milliseconds: 300),
);
β‘ Lazy Loading #
// Builder pattern
final analyticsModule = LazyModuleBuilder('analytics')
.withName('Analytics Dashboard')
.withLoader(() async => AnalyticsDashboardModule())
.withDependencies(['auth'])
.withStrategy(PreloadStrategy.whenIdle)
.withConfig(PreloadConfig(maxRetries: 3))
.build();
// In UI: show loading state automatically
LazyModuleWidget(
module: shopDeferredModule,
builder: (ctx, module) => ShopHomePage(),
loading: const ShopSkeletonScreen(),
error: (err, retry) => ShopErrorPage(onRetry: retry),
)
Preload Strategies #
| Strategy | When |
|---|---|
onDemand |
Only when explicitly called |
onFirstNavigation |
When user navigates to the module |
afterAppReady |
Shortly after all eager modules initialize |
afterDelay |
After a configurable delay |
whenIdle |
When the device is idle (3s after startup) |
onWifiConnection |
When Wi-Fi is available |
π Plugin System #
class FirebaseAnalyticsPlugin extends AnalyticsPlugin {
@override String get pluginId => 'firebase_analytics';
@override bool get initializeBeforeModules => true;
@override
Future<void> initialize(GlobalInjector injector, ModuleEventBus bus) async {
await Firebase.initializeApp();
injector.registerSingleton<FirebaseAnalytics>(FirebaseAnalytics.instance);
}
@override
Future<void> trackScreen(String name, {String? moduleId, ...}) async {
await FirebaseAnalytics.instance.setCurrentScreen(screenName: name);
}
}
π Dependency Injection #
// In onRegister β module-local services
injector.registerLazySingleton<CartRepository>(
() => CartRepositoryImpl(http: injector.get<HttpClient>()),
);
// Expose a service globally (other modules can use it)
injector.exposeGlobally<AuthService>(() => FirebaseAuthService());
// In widgets β access from widget tree
final cartService = ScopedLocator.of(context).get<CartRepository>();
// or with extension:
final cartService = context.read<CartRepository>();
π§ͺ Testing #
// Use MockModule for isolated tests
final mockAuth = MockModule(
id: 'auth',
moduleName: 'Auth',
onInitCallback: () => print('Auth init!'),
);
// Use MockEventBus to verify events
final bus = MockEventBus();
bus.emit(UserSignedInEvent(userId: '1', ...));
expect(bus.eventsOfType<UserSignedInEvent>().length, equals(1));
ποΈ Architecture Overview #
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MicrofrontendApp β
β (Bootstrap: registry + event bus + plugins + router) β
βββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββββββ¬ββββββ
β β β
PluginRegistry ModuleRegistry ModuleRouter
(Analytics, (Lifecycle + (All Routes
Sentry, Flags) DI + Events) from all
Modules)
β
βββ GlobalInjector (shared services)
β
βββ AuthModule ββββ ModuleInjector (scoped)
β EventAwareModule (publishes/subscribes)
β
βββ HomeModule ββββ ModuleInjector (scoped)
β Subscribes to AuthModule events
β
βββ DeferredModule (ShopModule) ββ Not loaded until needed
DeferredModule (SettingsModule) ββ Loads when idle
π Package Structure #
lib/
βββ flutter_microfrontend.dart β Single barrel export
βββ src/
βββ core/ β Registry, Loader, Lifecycle, App
βββ contracts/ β MicroModule + all mixins
βββ navigation/ β ModuleRouter, RouteRegistration
βββ events/ β EventBus, ModuleEvent, EventChannel
βββ injection/ β ModuleInjector, GlobalInjector, ScopedLocator
βββ lazy/ β DeferredModule, LazyModuleBuilder, PreloadStrategy
βββ plugins/ β MicroPlugin, PluginRegistry, specialized contracts
βββ state/ β ModuleStateScope, SharedStateBridge
βββ widgets/ β ModuleBoundary, LazyModuleWidget, ErrorBoundary
βββ utils/ β Logger, Validator, DependencyGraph
π Design Principles #
- Isolation First β Modules cannot directly import each other
- Lazy by Default β Load only what's needed, when it's needed
- Explicit Contracts β Every module declares what it provides and needs
- Pluggable β Add/remove plugins without touching modules
- Observable β Every lifecycle transition is emitted to a stream
π Dependencies #
| Package | Purpose |
|---|---|
rxdart |
Reactive Event Bus (PublishSubject, BehaviorSubject, operators) |
get_it |
Scoped DI containers |
go_router |
Modular routing |
equatable |
Value equality for events |
logging |
Structured logging |
meta |
Annotations (@mustCallSuper, etc.) |
π License #
MIT β see LICENSE