forge_mvvm 1.0.0
forge_mvvm: ^1.0.0 copied to clipboard
Opinionated MVVM + Clean Architecture framework for Flutter with runtime guardrails, typed state primitives, DI modules, and production scaffolding.
forge_mvvm #
An opinionated Flutter MVVM + Clean Architecture framework with real runtime guardrails, typed async primitives, module-based DI bootstrap, and practical CLI scaffolding.
What 1.0 solves #
forge_mvvm is designed for teams that want structure without ceremony.
- Strong architectural defaults through
ForgeModule+ForgeRegistrar - Runtime dependency-graph validation for layer direction
- Typed operation outcomes (
ForgeResult<T>) with stack traces - Typed command execution (
ForgeCommand<P, R>) with concurrency policies - ViewModel busy/error lifecycle helpers (
runGuarded, keyed busy state) - Navigation intents from ViewModels (no
BuildContextleakage) - Form ViewModels with field registry, validators, dirty/touched state
- Robust pagination state for initial/load-more/refresh/retry flows
- CLI that scaffolds complete feature modules with tests and route/module stubs
Architecture model #
UI (ForgeView / ViewModel / Command / Form / Pagination / NavigationEmitter)
-> Domain (UseCases + Repository contracts)
-> Data (Repository implementations + Services)
forge_mvvm enforces this with:
- compile-time contracts (abstract methods, typed APIs)
- runtime registration metadata (
layer,dependsOn) - bootstrap validation errors when dependency direction is invalid
Install #
dependencies:
forge_mvvm: ^1.0.0
flutter pub get
Quick start #
1) Define a module #
class LoginModule extends ForgeModule {
@override
String get name => 'login';
@override
void register(ForgeRegistrar registrar) {
registrar.lazySingleton<AuthServiceImpl>(
AuthServiceImpl.new,
layer: ForgeLayer.service,
);
registrar.alias<AuthService, AuthServiceImpl>(
layer: ForgeLayer.service,
);
registrar.lazySingleton<AuthRepositoryImpl>(
() => AuthRepositoryImpl(ForgeLocator.get<AuthService>()),
layer: ForgeLayer.repository,
dependsOn: <Type>[AuthService],
);
registrar.alias<AuthRepository, AuthRepositoryImpl>(
layer: ForgeLayer.repository,
);
registrar.factory<LoginViewModel>(
() => LoginViewModel(ForgeLocator.get<AuthRepository>()),
layer: ForgeLayer.viewmodel,
dependsOn: <Type>[AuthRepository],
);
}
}
2) Bootstrap app #
await ForgeApp.bootstrap(
modules: <ForgeModule>[
HomeModule(),
LoginModule(),
ArticlesModule(),
],
);
3) Build a ViewModel with guarded async work #
class LoginViewModel extends ForgeViewModel with ForgeNavigationEmitter {
LoginViewModel(this._repository);
final AuthRepository _repository;
Future<void> login(String email, String password) async {
final result = await runGuarded<ForgeResult<User>>(
() => _repository.login(email, password),
busyKey: 'login',
);
result.fold(
onSuccess: (loginResult) {
loginResult.fold(
onSuccess: (_) => replace('/'),
onFailure: (error, stackTrace) =>
setError(error.toString(), error: error, stackTrace: stackTrace),
);
},
onFailure: (_, __) {},
);
}
}
4) Handle navigation in View #
class LoginView extends ForgeView<LoginViewModel> {
const LoginView({super.key});
@override
LoginViewModel createViewModel(BuildContext context) =>
ForgeLocator.get<LoginViewModel>();
@override
Widget buildView(BuildContext context, LoginViewModel vm) {
return ForgeNavigationListener(
stream: vm.navigationIntents,
navigator: ForgeLocator.get<ForgeNavigatorAdapter>(),
child: const Scaffold(body: Placeholder()),
);
}
}
Public APIs #
Core #
ForgeApp.bootstrap(...)ForgeModule,ForgeRegistrarForgeBootstrapConfig,ForgeBootstrapReportForgeLocator
Result and command #
ForgeResult<T>(fold,map,mapError,error + stackTrace)ForgeCommand<P, R>(drop/queueconcurrency)
UI/ViewModel #
ForgeView<T extends ForgeViewModel>ForgeViewModel(runGuarded,isBusy,isBusyFor(key), structured errors)ForgeStateWidget
Navigation #
ForgeNavigationIntent(ForgePushIntent,ForgeReplaceIntent,ForgePopIntent)ForgeNavigationEmittermixinForgeNavigationListenerForgeNavigatorAdapterForgeGoRouterNavigator
Forms #
ForgeFormViewModelregisterField,setValue,validateField,validateAllerrorFor,isTouched,isDirty,clearErrors,resetForm
Pagination #
ForgePaginatedViewModel<T>(int-page)ForgeKeyedPaginatedViewModel<T, K>(cursor/token-based)ForgePaginationPhaseForgePageChunk<T, K>
CLI #
Run from package root:
dart run bin/forge_cli.dart <command>
Commands:
create feature <name>checktest
Feature flags:
--path(default:lib/features)--with-form--with-pagination--with-navigation--with-tests(default: true)--overwrite
Example:
dart run bin/forge_cli.dart create feature profile \
--with-form \
--with-pagination \
--with-navigation
Generated output includes:
- domain: entity, repository contract, use case
- data: dto, service contract + implementation, repository implementation
- ui: viewmodel + view (+ optional form/pagination vm)
- module + route stubs
- feature README
- test scaffold
Guardrails (what is and is not enforced) #
Enforced:
- runtime dependency direction validation from declared metadata
- missing DI registration errors with actionable messages
- duplicate registration policy handling
- no
BuildContextin ViewModels for navigation
Not fully enforceable in Dart (documented honestly):
- complete compile-time dependency direction across arbitrary user code
Use layer + dependsOn metadata consistently for strongest protection.
Testing #
flutter analyze
flutter test
or
dart run bin/forge_cli.dart check
The repository includes tests for:
- bootstrap/DI guardrails
- result/command/viewmodel behavior
- forms and pagination
- navigation emitter flow
- CLI scaffolding
- example smoke paths
Example app #
The example/ project demonstrates:
- module-first bootstrap
- home + login feature
- form validation feature
- paginated list feature
- navigation intents + listener adapter
- repository/service layering with DI metadata
Migration #
See MIGRATION.md for 0.2.x -> 1.0.0 upgrade guidance.
License #
MIT — see LICENSE