moarch
Flutter CLI — scaffold Clean Architecture projects with Riverpod or flutter_bloc, FVM and your own conventions.
6.5.0
5.7K downloads/mo
#3650 most downloaded on pub.dev
SuperMoooo/moarch
What this package is like to depend on
Last release today
23 Aug 2026
Ships fairly regularly
a new release about every 9 days
Nearly every release is documented
notes for 178 of 179 stable releases
Nothing withdrawn
no release was ever pulled
5 months old
179 releases · first in 2026
179 releases in the last 12 months
see the full history below
Release timeline
179 releases · Mar 2026 to Aug 2026Releases
latest 60 of 179-
6.5.023 Aug 2026Release notes
Open source →Fixed
moarch init --alldid not resolve on either stack.file_picker: ^11pinswin32 ^5.9, andflutter_secure_storage: ^11reacheswin32 ^6.0.1throughflutter_secure_storage_windows— the two cannot both be installed. file_picker is now^12.0.0, which is where it stopped depending on win32 directly. 12 also dropped theFilePickerResultwrapper (pickFilesreturnsList<PlatformFile>) and deprecatedpickFiles(allowMultiple: false), somedia_service.dartnow branches toFilePicker.pickFilefor a single pick.- A Riverpod project could not install
mogen_integration_tests. 1.1.2 needsanalyzer >=13, which a Riverpod app cannot reach: riverpod 3.4.2 declarestestas a regular dependency, andtestresolved against flutter_test's pinnedmatcher/test_apicaps analyzer below 13. The constraint is now^1.1.1, which gives pub somewhere to back off to — a bloc project still installs 1.1.2, a Riverpod one takes 1.1.1. (The real fix is upstream:mogen_unit_testsdeclaresanalyzer >=10 <15and never conflicts;mogen_integration_testsnarrowed its floor to 13.) - The generated Riverpod project did not compile against flutter_riverpod 3.
ProviderListenable— the type everyref.listentakes, and the onelistenActionis declared with — left the main barrel in Riverpod 3.action_listener.dartnow importspackage:flutter_riverpod/misc.dart, where it lives. AppAsyncViewbuilt its stream/future states withAsyncValue.copyWithPrevious, which went@internalin Riverpod 3 and warned in every generated project. It now trackshasValue/value/error/isLoadingitself and adapts the provider'sAsyncValuethrough public getters only. Behaviour is unchanged: a reload or a failed refresh still leaves loaded data on screen.moarch create featuregenerated the repository, its implementation, the entity and the model even when the Repository row was left unticked — the state holder was assumed to need one, so the layer was silently added back. The checklist is now taken literally: unticking Repository generates a bloc that takes nothing and loads nothing until you point it somewhere, and a Riverpod notifier whosebuild()is a TODO with no locator behind it. Both are registered and both compile.
Changed
- The generated bloc is four states and one event.
Initial,Loading,Success,Failure, andStarted— dispatched when the screen opens and again to refresh or retry.Refreshedis gone: a retry is the same load, and two names for it is one too many. Successis generated empty. It carried aList<XEntity> items, acopyWithand aplaceholderof fake rows. What a screen shows is the screen's business, and a scaffolded list half the features do not want is a line to delete rather than a head start — so it ships asconst XSuccess()with a TODO saying where the fields and theirpropsgo. The state no longer names an entity at all, which is what lets a feature without a data layer compile.- The Firestore live variant of the bloc is gone, and with it
create bloc --firestore. It brought two events of its own (ItemsUpdated,Failed), aStreamSubscriptionand aclose()override. Every bloc is now the one-offawait _repo.fetchAll()shape whatever the backend is; a live query is the project's to wire. The data layer is untouched — a Firestore datasource still haswatchAll()and the repository still declares it. - The view still shimmers while loading, traced over a stand-in
const XSuccess()instead of aplaceholderstatic, with a comment saying to give the fields fake values as they are added. It no longer importsEmptyView, which only the live variant had a use for. - Comments across the generated bloc, event, state, page and view are cut back to what is load-bearing.
moarch create blocregisters the bloc even when the feature has no repository — it takes nothing, so there is nothing to wait for.
-
6.4.022 Aug 2026Release notes
Open source →Features
AppBottomNavtakes aborderColor. Floating, it is a hairline around the card, drawn as part of the shape Material already cuts the corner with; docked, it replaces theoutlineVariantline between the bar and the content, and gives Material's own bar a top edge it never drew. Null is the bar every project had before this — a flat dark theme is where it earns its place, since the shadow holding a floating card up is invisible there.AppBottomNavtakes afloatingWidth.AppBottomNavWidth.hugsizes the floating card to its destinations and centers it, instead of spanning the screen — two or three tabs stretched across a phone is mostly empty card.floatingMaxWidthcaps the width of either, which is how afillbar stops short of the edges on a tablet. Both are only read whenfloating: a docked bar is the bottom edge.- The styles the widget draws itself hug by way of a min-size row; Material's
own
NavigationBardivides whatever width it is handed, so hugging measures it with anIntrinsicWidthinstead.
- The styles the widget draws itself hug by way of a min-size row; Material's
own
AppAdaptiveNavhands the phone layout the three new knobs asbottomNavBorderColor,bottomNavWidthandbottomNavMaxWidth.- The generated design-system screen previews the new looks: a hugging bordered pill bar, a capped Material one, and a docked bar with a colored top edge.
-
6.3.122 Aug 2026Release notes
Open source →Features
core/utils/extensions.dartgainsFormX.isValidonGlobalKey<FormState>, so a submit readsif (_formKey.isValid)instead of_formKey.currentState?.validate() ?? false. An unmounted form reports invalid, which is the safe half of that??.
-
6.3.022 Aug 2026Release notes
Open source →Features
core/utils/extensions.dartgainsFormX.isValidonGlobalKey<FormState>, so a submit readsif (_formKey.isValid)instead of_formKey.currentState?.validate() ?? false. An unmounted form reports invalid, which is the safe half of that??.
Fixed
-
The generated
main.dartnow removes the native splash afterrunApp()rather than before it. Removing it first hands the screen back to the framework before a single frame has been painted, which is a blank window for as long as the first build takes — on a cold start with a router that parks on a loading route, that flash is visible. The comment above the call says how to push it later still (your own async init, or a post-frame callback) if something has to land before the app is on screen. -
flutterbloc: the app-wide
BlocProvider<AuthBloc>is nowlazy: false. A lazy provider builds its bloc on the first read _from the widget tree, and nothing reads this one that way — the router's redirect and itsrefreshListenableboth take the bloc out of the locator. So the..add(const AuthStarted())increatenever ran, session restore never started, and the router sat on the splash route waiting for a state change that could not arrive.
-
6.2.021 Aug 2026Release notes
Open source →Features
-
initnow writeslib/core/network/paginated.dartalongsidesafe_api_call.dartwhenever Dio is part of the stack —Paginated<T>, the envelope a REST list endpoint answers with. It exists so the first feature to paginate has one shape to share rather than one per repository.Shaped for the common
{page, limit, total, data}response, but written to be edited: the envelope is the backend's choice, not the app's. The item key is adataKeyargument rather than a literal, and the counts are read leniently — a missing or stringifiedtotal, which a last page or a PHP backend will hand you, costs a fallback instead of aTypeErrorthatsafeApiCallcan only report as an unknown failure. A null item list reads as an empty page for the same reason.fromJsonTtakes anObject?rather than aMap, so a page of scalars parses through the same factory as a page of models.What survives renaming the fields is the arithmetic and the two members the Clean Architecture boundary is there for:
map, so a repository hands the domain a page of entities instead of a page of models, andappend, which is the whole of "load more" — the state holds onePaginatedand replaces it withstate.append(page).pageCounttreats a zerolimitas a single page, so an envelope that carried no page size cannot loop a load-more forever.Nothing generated consumes it yet:
fetchAll()still returns a whole list and the feature states still hold a plainList. Wiring aloadMorepath through the notifier and bloc templates is a separate change; a project whose API never pages can delete the file. -
paginatedis a catalog entry, somoarch update paginatedrefreshes it,moarch update networkincludes it, and--diffshows what a refresh would change.
-
-
6.1.021 Aug 2026Release notes
Open source →Features
-
initnow writes the project's ownREADME.md— the one generated document aimed at a person rather than a task, written for someone who has never worked on a Clean Architecture Flutter app. Thirteen sections: what the project is and why the dependency rule shapes the folders; a first-run walkthrough from installing FVM through.envandbuild_runnertofvm flutter run, with a table of what to do when each step fails; the tooling and the packages that shape how the code is written; an annotated tree; one piece of data traced entity → model → repository → get_it →AppException; the state stack the project actually took;moarch create featureand the five steps that are still yours; flavors; the build commands and where the artefacts land; the workflows and every secret they read; the conventions the analyzer cannot check; contacts and access; and where the rest ofdocs/picks up.It replaces the README `flutter create` leaves behind and only that one — matched on two of its own sentences, the same way `main.dart` and `widget_test.dart` already are, so a README you wrote is never touched. -
The README is a catalog entry, so
moarch update readmerefreshes it and--diffshows what a refresh would change. Like every other template it reads its options back off the project rather than remembering them: the state stack, the packages, the workflows, and now the flavors —initruns beforemoarch create flavorsexists, so a fresh project gets the flavor setup walkthrough, and the same file rewrites itself with the real flavor names, run commands and artefact paths onceflavorizr.yamlis on disk.Two sections are
[bracketed]placeholders instead: the owner line and Contacts & access, which no detection can fill in. -
ScaffoldContextgainedprojectName,flavorNamesandhasWorkflows.flavorNamesscansflavorizr.yamlrather than parsing it as YAML: that file is edited by hand, and a malformed one should cost the README its flavor list, not make every template in the catalog throw.
-
-
6.0.020 Aug 2026Release notes
Open source →Breaking on update, not on init. A project scaffolded with 6.0.0 is fine. An existing 5.x project is only affected if you run
moarch update— and then in one specific way:buildDioClienttakes a new requiredrefreshSession:argument, andinjector.dartis the file that passes it.updaterefreshesdio_client.dartsilently (you almost certainly never edited it) but leavesinjector.dartalone, becausemoarch create featurewrites into it and that counts as edited. The two then disagree and the project stops compiling.Refreshing both together is the fix:
moarch update dio-client moarch update injector --force # only if you have no hand-edits to keepOr add the argument by hand:
..registerLazySingleton<Dio>( () => buildDioClient( getIt<TokenStorage>(), refreshSession: () => getIt<AuthRepository>().refresh(), ), )Two smaller ones in the same shape:
auth_remote_datasource.dartnow callsApiConstants.authLoginand friends, so refreshapi-constantsalongside it or the datasource will not resolve them.- Refreshing a state holder switches it from
GetXtoXRepository. That compiles as-is — the repository was always registered too — but leavesdomain/usecases/get_x.dartand itsregisterLazySingleton<GetX>orphaned. Both can be deleted.
Changed
- Generated
pubspec.yamlnow carries a caret constraint on every dependency instead ofany, from one table inlib/src/utils/package_versions.dartthat is bumped per release.intlstays unconstrained on purpose:flutter_localizationspins it exactly from the SDK. - Use cases are no longer generated. A
GetXthat only forwarded_repository.fetchAll()added a name and no behaviour, and the auth featureinitscaffolds never had one — so the two halves of the generator disagreed about whether the layer existed. State holders take the repository in both stacks. - The refresh-token protocol is written once.
dio_client.dartno longer carries its own bare Dio, endpoint and JSON keys beside the datasource's: the 401 interceptor takes arefreshSessioncallback,injector.dartpasses the auth repository'srefresh(as a callback, since the repository is built on that same client), and the repository — a lazy singleton — owns the single-flight guard that used to be a top-level mutable global. /auth/*paths moved intoApiConstants, so the datasource and the Dio client's public-route list read the same strings.safeApiCallrecognises offline from what Dio throws rather than askingconnectivity_plusbefore every request — that cost a platform round-trip per call and reported a captive portal as online.safeFirebaseCallkeeps its pre-flight; its doc comment now says so.- Every
moarch createsubcommand accepts either alib/or the project root for--path.
Fixed
moarch create featurein a bloc project scaffolded Riverpod when--pathpointed at the project root:StateManagement.detectonly looked one directory up forpubspec.yaml, and falls back to Riverpod when it finds none. It now walks up until it finds one.moarch updaterewrote a bloc project'sanalysis_options.yamlas the Riverpod one, dropping thebloc:ruleset — the catalog spec calledanalysisOptions()without the stack.- A failed account deletion is reported instead of swallowed.
AuthFailurenow carries the session it failed from (userId), so the router redirect can tell "signed out, with a reason" from "still signed in, and something went wrong" — which is what made the failure unemittable before. The same change gives the Firebase password-reset handler distinct states for two identical failures in a row, where Equatable had deduped the second emit and shown nothing. debugLogDiagnosticsand Dio'sLogInterceptorare debug-only.appLoggeralready dropped the log records in release, butmsg.toString()runs at the call site, so every response body in the app was still serialised in full first.
Removed
AppRoutes.designView(pointed at a screeninitdoes not generate) andAppRoutes.forgotPassword(unrouted, and silently inpublicRoutes).AppExceptionType.cacheand.parsing, which nothing ever constructed, and theAppException.test()factory — a test helper shipped inlib/.
Added
.env.example, committed beside the gitignored.env. Without it a fresh clone had no.envat all andbuild_runnerfailed onapp_env.dartbefore a new developer could run anything.
-
5.0.416 Aug 2026 -
5.0.316 Aug 2026 -
5.0.216 Aug 2026 -
5.0.115 Aug 2026Release notes
Open source →A bloc's page is a file of its own
moarch create featureon the bloc stack writespresentation/pages/<x>_page.dartbesidepresentation/views/<x>_view.dart. The page is theBlocProvider— it builds the bloc out of the locator, opens it with its first event, and is what aGoRoutepoints at. The view is left a plain widget that reads the bloc off the context, so a widget test can pump it with a bloc of its own without the locator being set up.Riverpod generates no page: a notifier is read through a provider wherever it is needed, so there would be nothing to wrap the screen in. The auth screens are unchanged in both stacks — their holder is provided once, above the router.
-
5.0.015 Aug 2026Release notes
Open source →Breaking for both stacks. A project generated with 5.0.0 does not look like one generated with 4.x. Nothing migrates an existing project:
moarch updaterefreshes a file where the current templates put it, so a 4.x bloc project keeps itspresentation/states/folder — andupdatestops refreshing what is in it — until the files are moved by hand. A 4.x Riverpod project keeps its providers and is not touched.Riverpod uses get_it for dependency injection
Riverpod declared a provider beside every class it built. It no longer does.
lib/config/di/injector.dart— the file the bloc stack has had since 4.0.0 — is now generated for both stacks, and holds the same things in both: clients, services, datasources, repositories and use cases.- Gone from a Riverpod project:
dioClientProvider,secureStorageProvider,tokenStorageProvider,firebaseAuthProvider,firebaseDbProvider,permissionProvider,mediaServiceProvider,urlLauncherProvider,notificationServiceProvider,firebaseNotificationsServiceProvider,biometricServiceProvider,connectivityProvider,debouncerProvider,dialogProvider,modalProvider, and the per-feature<x>RemoteDataSourceProvider/<x>LocalDataSourceProvider/<x>RepositoryProvider/get<X>Provider. Each is agetIt<Thing>()now. - Riverpod holds the state; get_it holds everything the state is built
from. What stays a provider is what actually holds state: the feature
notifiers,
authNotifierProvider,languageProvider,routerProvider,hasInternetProviderandmaintenanceStatusProvider— and those read their dependencies out of the locator. - A notifier is the seam between the two.
AsyncNotifierneeds theRefonly Riverpod can hand it, so it is not registered in get_it; it declaresOrdersRepository get _repo => getIt<OrdersRepository>();in place ofref.watch(ordersRepositoryProvider). moarch create featurenow registers what it generated in a Riverpod project too — the datasource, the repository and the use case, at the// moarch:registrationsanchor. The notifier is the only thing it leaves out, because there is nothing to register.main.dartcallsawait setupInjector()beforerunAppin both stacks. TheProviderContainer/UncontrolledProviderScopedance a Riverpod project needed when a service held aRefis gone with the services: it is a plainProviderScopeagain, whatever is selected.get_itis a dependency of both stacks.moarch doctorchecks for it and for the locator in both.- The services that only differed in how they were reached — secure storage,
biometrics, permissions, media, URL launcher, notifications, FCM, debouncer,
dialogs, modals,
AppButton— now have one body instead of two.config/firebase/firebase_providers.dartandcore/network/dio_client.dartare the same file in both stacks and moved out of the per-stackAppTemplates.
A bloc's state lives with its bloc
presentation/states/<x>_state.dartmoves topresentation/blocs/<x>_state.darton the bloc stack, beside the events and the bloc. The three are one unit — the handlers emit the states — and a change to any of them is usually a change to all three. Riverpod is unchanged:presentation/states/besidepresentation/notifiers/.Bloc views are
BlocConsumerThe generated view is a
BlocConsumerrather than aBlocBuilder, with alistenerprepared for the states the feature has andlistenWhen: (previous, current) => previous != current. It is the bloc answer toref.listen:listenerruns once per new state — where a toast, a dialog or acontext.pushbelongs — whilebuilderruns on every rebuild.Fixes
moarch create blocwrote Riverpod'score/utils/action_notifier.dartinto a bloc project, importingflutter_riverpodin a project that does not have it. It also named a mixin (ActionBlocMixin) that has never existed. The file is no longer written.- The Riverpod feature notifier declared a repository getter it never called,
which the analyzer reports as an unused element, and imported the use case
without using it. It now calls its dependency in
build()and takes the use case when there is one — the same rule the bloc has followed since 4.0.0 (the repository regardless on the Firestore variant, whose live query no use case wraps). - The Riverpod repository implementation imported
app_exception.dartwithout using it, and the local datasource imported its model without using it. - The bloc feature state's
--firestorevariant declared its constructor asconst OrdersSuccess{...}— no parentheses around the parameter list, so the generated file did not parse. It isconst OrdersSuccess({...})now.
- Gone from a Riverpod project:
-
4.0.014 Aug 2026Release notes
Open source →Riverpod projects are unaffected by this release — every template, field and file path on that side is unchanged. Everything below is the new stack.
Features
- flutter_bloc is a supported stack.
moarch initnow asks which state management the project uses before anything else — Riverpod, as before, or flutter_bloc withget_itfor dependency injection. Every state-bearing template exists in both, underlib/src/templates/riverpod/andlib/src/templates/bloc/: the feature scaffold, both auth features,AppAsyncView, the action listener, the maintenance gate, the router, the Dio client andmain.dart. Same layers, same file names, same layer boundaries — a project reads the same way whichever it took. - A bloc feature is sealed on both sides: a
<Feature>Eventfamily, and a<Feature>Statefamily ofInitial/Loading/Success/Failure, so the view is aswitchthe compiler checks for completeness. The family is the status — there is no flag or enum on top of it. States and events extendEquatable, which is load-bearing: bloc drops an emit equal to the current state andBlocBuilderrebuilds on the same test, so without it every emit repaints. AuthStatefollows the same shape:AuthInitial(restoring, which is what parks the router on splash),AuthLoading,AuthAuthenticated,AuthUnauthenticated,AuthFailure.- Bloc views are plain flutter_bloc:
BlocProviderin aPage,BlocBuilderand aswitchin theView.AppAsyncView,ref.listenActionand any shared action base are not generated into a bloc project — they exist to map Riverpod's opaqueAsyncValueonto four screens, and a sealed family needs no wrapper.moarch create widget async-viewsays so rather than writing a file that cannot compile. moarch create featurereads the stack offpubspec.yamland generates for it — no new flag. In a bloc project it also registers what it generated inlib/config/di/injector.dart, at the// moarch:registrationsanchor.moarch create bloc <feature> <name>adds a state + event + bloc trio to a feature that already exists, wired to that feature's repository.moarch init --state riverpod|blocpicks the stack without the checklist, so--allcan reach either one.- Generated bloc projects get
bloc_lintin dev dependencies, the recommended ruleset inanalysis_options.yaml, and abloc lintstep in the CI workflow. A freshly scaffolded project passes it with no findings. moarch doctorchecks what the project's own stack needs:get_itand a locator with its anchor comment for bloc,flutter_riverpodotherwise.
Changes
main.dart,action_notifier.dart,app_router.dart,dio_client.dart,firebase_providers.dartandlanguage_service.dartmoved out ofCoreTemplates/ConfigTemplates/ServicesTemplatesinto the per-stackAppTemplates. Nothing changes in what a Riverpod project generates.- Templates that differ only in how a service is reached — the services, secure
storage, the biometric service,
AppButton, the dialog and modal helpers, the design-system preview — take the stack as a parameter instead of being duplicated, so there is one body to maintain.
- flutter_bloc is a supported stack.
-
3.2.213 Aug 2026Release notes
Open source →Fixes
moarch create widgetrecorded only the files it wrote this run, so a widget already on disk stayed out of.moarch.yaml— andmoarch updatethen read it as a file it could not vouch for.create widget <name>andcreate widget allnow also record a widget they skipped when its content is still exactly what the current templates generate, whether it got there from an earlier run, a copy, or a run that stopped before saving. A file that differs is still left out: that content is yours.
-
3.2.113 Aug 2026Release notes
Open source →Features
AppBottomNavtakes two more looks apart from itsstyle.labels(auto/below/none) says where the destination names are written, so the pill can stack over its label instead of opening sideways, the dot can carry one at all, and any style can drop to icons only — a label that is not drawn still reaches a screen reader and still names its icon on a long press.floatingShape(full/rounded/square) cuts the floating card's corner, andpillShapethe corner of the fill behind the selection — which Material's own bar reads too, as its indicator. Both take aBorderRadiusof the project's own (floatingBorderRadius,pillBorderRadius) where the three names are not the number wanted.AppAdaptiveNavpasses all four down asbottomNavLabels,bottomNavShape,bottomNavPillShapeand their radius pair. Defaults are what the bar drew before.- The dark theme is now a choice.
initasks for it (Dark theme, off by default): with it off,AppConstantsdeclares one brand palette andAppThemeonelightgetter — around 290 fewer lines in the files you actually edit. With it on, every color token gains its*Darkcounterpart,AppTheme.darkis generated, andmain.dartgetsdarkTheme+themeMode: ThemeMode.system. moarch create theme --darkadds the dark half to a project scaffolded without it, and--no-darktakes it away again. The palette, the theme,main.dart,AppToastand the design-system preview are generated against each other, so the switch is all of them at once: files moarch wrote and nobody edited are rewritten silently, and an edited one stops the run with a diff instead (--diff,--dry-run,--force,--yes).- The scope is read off
app_theme.dartrather than remembered, somoarch updateandmoarch create widgetfollow what the project actually is — including after switching.
Fixes
main.dartshippeddarkTheme: AppTheme.darkcommented out, so a generated app was light-only whatever the palette said. The design-system preview had the same line commented out under a working brightness toggle, leaving a button that did nothing. Both are now wired when the project takes dark.
Changes
AppConstantsdrops the tokens nothing in the kit read:accentActive,accentRestorative,accentEnergetic(the tab indicator usesprimary),padding8,paddingH16,paddingH24,paddingV16,borderRadius24andduration100. The remaining colors are grouped brand → surfaces → status, with the dark palette (when present) in one block rather than three.
Upgrading
An existing project keeps its dark theme — the scope is read off
app_theme.dart, somoarch updatesees what is already there. Two things to look at in the diff it offers:moarch update constantsremoves the tokens listed above. If your own code reads one of them, keep it: it is your palette now.moarch update mainuncommentsdarkThemeand setsthemeMode, which is what the dark palette was always for — but it does mean the app starts following the system brightness.moarch create theme --no-darkis the way out if it was never meant to.
-
3.2.012 Aug 2026Nothing published for this version
-
3.1.912 Aug 2026 -
3.1.812 Aug 2026Release notes
Open source →Fixes
AppTextButtonalignment now moves the label: it reaches the row and the text, and claims the parent's width to align inside of
Features
AppTextButtongainsbare, dropping the button box around the label
-
3.1.712 Aug 2026 -
3.1.612 Aug 2026 -
3.1.510 Aug 2026 -
3.1.409 Aug 2026Release notes
Open source →Fixes
-
The design-system preview renders in the app's real theme. It built its own
ThemeData(useMaterial3: true)behind aTODO, so the one screen whose job is showing what the kit looks like was the one screen not showing it — every widget previewed in stock Material colors and type instead of the project's. It now usesAppTheme.light/AppTheme.dark, the same themesmain.dartmounts, so editinglib/config/theme/app_theme.dartmoves the preview with it.app_theme.dartis written unconditionally byinit, so the new import needs nothing the scaffold did not already have. -
initanddoctornow surface thefvm usestep.initwrites a.vscode/settings.jsonpointingdart.flutterSdkPathat.fvm/flutter_sdk, but onlyfvm usecreates that symlink and.fvm/is gitignored — so on a fresh scaffold or a fresh clone the path did not exist. Nothing reports that: the Dart extension silently falls back to the first Flutter onPATH, and debug, hot reload and the analyzer all run the SDK the.fvmrcpin exists to avoid. The only symptom is analyzer output that disagrees withfvm flutter analyze.initnow printsfvm useas the first step, ahead ofpub get, andmoarch doctorgrew a check for it:-
dart.flutterSdkPathpointing at a path that does not exist — error, with thefvm usefix. -
the symlink present but dangling, the pinned SDK not installed — error, pointing at
fvm install. -
a versioned
.fvm/versions/<version>path, which is whatfvm userewrites the setting to and which stops following.fvmrc— warning, anddoctor --fixpoints it back at.fvm/flutter_sdk. -
settings.jsonmissing, or carrying nodart.flutterSdkPath— warning.An absolute path is left alone as a deliberate override, and a project with no `.fvmrc` gets none of these findings.
-
-
The README documents that the generated
.fvmrcpins thestablealias rather than a version, sofvm installon CI or a teammate's machine can resolve to a different SDK than your cache holds, and how to pin for real once the project ships.
-
-
3.1.308 Aug 2026 -
3.1.208 Aug 2026Release notes
Open source →Features
-
MaintenanceGate— a kill switch the backend owns. While a flag says maintenance, it replaces the whole app with a screen carrying the title and message the backend sent, so the team taking the API down can empty the app, and reword the notice, without a release. Mounted inMaterialApp.builderso it wraps the Navigator: above every route the router can reach, including anything pushed after the flag flips. It replaces rather than covers, so nothing is left to tap and the back button has nothing to pop. It fails open — loading, offline, endpoint down or rules denied all read as "up", because a fault in the check must not lock out every user at once. The provider follows the project's backend: a live Firestoresnapshots()listener, a polled Dio endpoint (five minutes, plus on resume), or a stub to point at your own source. Available in theinitchecklist and asmoarch create widget maintenance-gate. -
Widgets whose source varies with the project are now resolved in one place,
WidgetCatalog.sourceFor, instead of being special-cased separately ininit,create widgetandupdate— three copies that had to agree, orupdatewould report a file as edited the moment it was generated. -
initwritesandroid/app/proguard-rules.pro— the keep rules that were until now only printed indocs/SECURITY_BEFORE_DEPLOYMENT.mdfor you to copy across: the Flutter engine, Play Core, Firebase, OkHttp, coroutines, enums, native methods, andSourceFile,LineNumberTableso a release stack trace still de-obfuscates. The file is inert until the release build type turns R8 on, so enabling minification before a release is now just that gradle block rather than that block plus a round of release-only crashes. The doc renders the same template, so the two cannot drift. Refreshable withmoarch update proguard(newandroidgroup).
Docs
CHECKLIST_BEFORE_DEPLOYMENT.mdandSECURITY_BEFORE_DEPLOYMENT.mdreconciled with what the scaffold actually does. Both were generic checklists that asked you to do workinithad already done. Items the scaffold handles now arrive ticked and name the file that handles them (config/env/app_env.dart,TokenStorage,ValidationService,app_logger.dart, the CI jobs), so the OWASP mapping stays complete but you can see at a glance what is left. Everything unticked is genuinely yours.- Gaps the checklists implied were covered are now called out as gaps, with the
exact steps: no
.env.example, nonetwork_security_config.xml, R8 rules written but not enabled,build/debug-info/never uploaded by the Android workflow, andbuild_ipa.ymlarchiving throughxcodebuildwithout carrying the Dart obfuscation flags. - Corrected content that no longer matched the generator: the
enviedexample pointed atlib/core/env/env.dartand classEnv(the scaffold generateslib/config/env/app_env.dartandAppEnv), the R8 block was Groovybuild.gradlewhere the scaffold patchesbuild.gradle.kts, and two code examples had Portuguese UI strings in an otherwise English doc.
-
-
3.1.108 Aug 2026 -
3.1.007 Aug 2026Release notes
Open source →Features
moarch create flavors— sets a project up fordev/staging/prodflavors (or the names you pass) through flutter_flavorizr, configured so the project keeps onemain.dart— yours, untouched. It writes aflavorizr.yamlwhoseinstructionsrun only the native-side processors (android:flavorizrGradle,android:buildGradle,android:androidManifest,ios:xcconfig,ios:plist) plusflutter:flavors, and adds the dev dependency — sodart run flutter_flavorizrpatches the native side and generateslib/flavors.dart, and nothing else. No per-flavormain_<flavor>.dartentry points. The Android application id and iOS bundle id are read from the project, non-production flavors get suffixed ids so the builds install side by side, and the flavored entriesinitalready writes into.vscode/launch.jsonstart working.moarch create model --from-json <file>— hands the command a sample of the payload the API actually returns, and the entity and model come out with real fields instead of TODOs: a completefromJson/toJsonkeyed on the original JSON keys,fromEntity/toEntity, and==/hashCode. ISO-dated strings becomeDateTime, doubles parse throughnumso an int in the payload doesn't crash them, homogeneous lists keep their element type, snake_case keys become camelCase fields, and a top-level JSON list is sampled at its first element. Anullin the sample can only type asdynamic, and is called out so you can tighten it by hand.
Fixes
- The
.vscode/launch.jsontemplate carried a trailing comma that strict JSONC parsers flag, introduced in 3.0.0 — removed, and the template tests brought back in line with the 3.0.0 template rewrite. moarch init --dry-runlisted every file, including ones a real run would have skipped because they already exist. The preview now makes the same decision against the same disk as a real run.- A failed scaffold's rollback removed the files it created but left their empty directory chains behind — the directories are now removed too (only ever ones the run itself created, and only when empty).
moarch updatefailing partway through a refresh left the project half on the old templates and half on the new. The files already refreshed are now restored to what they held before.
Meta
- The changelog accumulates again. Each release used to replace the whole file, so pub.dev only ever showed the latest entry — the full release history below was restored from git.
topicsandissue_trackeradded topubspec.yaml.- CI now verifies
lib/src/version.dartmatchespubspec.yaml. example/example.dartrewritten to match the current CLI.
-
3.0.006 Aug 2026 -
2.9.406 Aug 2026 -
2.9.305 Aug 2026 -
2.9.205 Aug 2026 -
2.9.105 Aug 2026 -
2.9.005 Aug 2026Release notes
Open source →Features
- Firebase Auth is now a backend choice, not just a provider. Selecting it with
the auth feature generates that feature against Firebase instead of REST:
email/password, Google sign-in, password reset, account deletion, and a
session restored from
authStateChanges(). Same layers and provider names, no token storage, and Dio is no longer pulled in for it. moarch create featurefollows the project's backend: in a Firestore project the datasource holds_firestoreinstead of_dio, withfetchAll/fetchOne/watchAll/create/save/deleteover one collection and aStringdocument id. With both backends installed, the layer checklist asks which one the feature talks to.AppExceptionmaps the Firebase failures an app actually hits: afromFirebaseAuthErrorfactory for the auth codes (invalid-credential,email-already-in-use,weak-password,requires-recent-login,too-many-requests…) and the Firestore codes (permission-denied,unavailable…) infromFirebaseError. Newauthandcancelledtypes, plusAppException.cancelled()for a dismissed sign-in sheet.- New
core/network/safe_firebase_call.dart— the Firebase counterpart ofsafeApiCall, for one-off calls and for streams. - New
docs/FIREBASE_SETUP.mdcovering the work that lives outside Dart:flutterfire configure, enabling the sign-in providers, the Android SHA-1/SHA-256 fingerprints, the iOSGIDClientIDandREVERSED_CLIENT_IDURL scheme, the web client id, and a starting set of Firestore rules. initwrites the two iOS Google sign-in keys intoInfo.plist, taking the real values fromGoogleService-Info.plistwhen it is already there and leaving documented placeholders when it isn't. Existing URL types are kept.
Fixes
main.dartnow callsFirebase.initializeApp()for Firestore and Firebase Auth, not only for Crashlytics — a project with either selected used to throw "No Firebase App '[DEFAULT]' has been created" on its first provider read.
Doctor
- New checks for a half-wired Firebase project: missing
firebase_coreorgoogle_sign_in, noFirebase.initializeApp()inmain.dart, a missinggoogle-services.json/GoogleService-Info.plist, andInfo.pliststill carrying the placeholder Google client ids — which--fixfills in fromGoogleService-Info.plist.
- Firebase Auth is now a backend choice, not just a provider. Selecting it with
the auth feature generates that feature against Firebase instead of REST:
email/password, Google sign-in, password reset, account deletion, and a
session restored from
-
2.8.104 Aug 2026 -
2.8.002 Aug 2026Release notes
Open source →Features
moarch updatenow refreshes everything the CLI generates, not just the widget kit. The gap it closed for widgets was the same gapcore/,config/, the auth feature, the docs and the workflows had all along: a project scaffolded two versions ago still carries the oldvalidation_service.dart, and nothing told you which improvements you were missing or which changes were your own.- Every file is addressable on its own —
moarch update validation,moarch update extensions,moarch update theme,moarch update logger. With no arguments the whole project is considered, exactly as before. - Or by group, when a whole area has drifted:
widgets,core,network,security,services,config,auth,docs,workflows,project,ios. They combine freely —moarch update security docs extensions. moarch update --listprints every name and group with the file it maps to, so the slugs don't have to be guessed.- Templates that vary are rebuilt against the project they land in, not
against a default:
app_logger.dartkeeps its Crashlytics branch,main.dartkeeps the router, localization and notification services the project actually has,app_exception.dartkeeps its Dio and Firebase mappings, andbuild_ipa.ymlkeeps its Firebase steps. The options are read back off the generated files andpubspec.yaml— the record that stays true as the project is edited. - It refreshes, it never scaffolds. A file the project declined at
initis not missing, so naming it does nothing rather than generating it.moarch update biometricin a project without biometrics is a no-op. - The three buckets are unchanged, and now apply to all of it: untouched
files refresh silently, edited ones are listed and diffed and never
written without
--force.
- Every file is addressable on its own —
Improvements
moarch initnow records every file it writes in.moarch.yaml, where it previously recorded only the widgets. That record is the whole basis for telling an untouched generated file from one you edited — without it the rest of the scaffold could only ever be reported as needs review.- A project scaffolded before 2.8.0 has no record of its non-widget files,
so the first
moarch updatelists them as needing review even where they are untouched. Refreshing or confirming them re-records them, and subsequent runs are exact. That is the safe direction: nothing is overwritten on the strength of a guess.
- A project scaffolded before 2.8.0 has no record of its non-widget files,
so the first
.fvmrcandflutter_native_splash.yamlare generated fromDevTemplatesrather than from literals inside the init command, so whatinitwrites and whatupdatecompares against cannot drift apart.
-
2.7.131 Jul 2026Release notes
Open source →Features
AppAudioPlayer(moarch create widget audio-player) — an audio player over just_audio that a screen configures rather than wires. It owns theAudioPlayer, loads the source and disposes both. OneAppAudioSourcecovers url, asset and file.- Every part is a switch, so the same widget is a podcast screen and a
voice-note bubble:
showControls,showSkip,showProgress,allowScrub,showTimes,showRemainingandshowSpeedare independent, andAppAudioPlayerStyle.compactis the one-row arrangement. - The skip buttons take durations, not a fixed 15/30 — the number is drawn inside the arrow, so any interval works without an icon per value.
- Buffered progress rides in the bar's secondary track; a scrub is not dragged
back by the position stream mid-drag; a finished clip restarts on the next
tap rather than sitting at the end; and
onCompletedfires once per play-through rather than on every frame the player sits incompleted.
- Every part is a switch, so the same widget is a podcast screen and a
voice-note bubble:
AppDragSection(moarch create widget drag-section) — a section whose children drag into a new order, vertical or horizontal, with no dependency.- It reports the move rather than owning the list, so the order can live
in a notifier, in storage or on a server without the widget holding a
second copy of it.
AppDragSection.reorderdoes the remove-and-insert. - Each item declares its own size —
AppDragSize.small/medium/largeoff a sharedAppDragSizes, or an exactextent— and whether it can be moved. - A pinned item is a wall, not merely un-draggable: it carries no drag listener at all, and nothing can be dropped past it, so an "add" tile keeps the last slot however the rest are shuffled.
onReorderarrives already corrected for theReorderableListViewoff-by-one and for any pinned item in the way.- A long press starts the drag, because an immediate listener over the whole
item fights the scroll;
AppDragTrigger.handleputs a grip on the trailing edge instead.
- It reports the move rather than owning the list, so the order can live
in a notifier, in storage or on a server without the widget holding a
second copy of it.
AppTable(moarch create widget table) — rows and columns sized for a phone, with no dependency.- Columns are fixed (
width) or flexible (flex), and a flexible one never squeezes below itsminWidth. Past the point where the minimums no longer fit, the table pans sideways rather than crushing the columns. AppTableColumn.numericright-aligns and switches on tabular figures.- Rows take
onTap,selectedand a colour of their own;striped,showRowDividers,showColumnDividers,showBorderanddensitydecide the rest. Cells are strings, orwidgetsfor a chip or an avatar. - It deliberately owns no vertical scroll — a table that scrolls
vertically cannot sit in a page that also does. Put it in
AppSingleScrollViewor aListView.
- Columns are fixed (
AppCountryPicker(moarch create widget country-picker) — the 238-countryAppCountrytable as a field of its own, validating like the rest of the family, or asAppCountryPicker.show(context)from anywhere that is not a form.- It hands back the whole
AppCountryrather than a code, since the caller usually wants the dial code or the flag too.displaypicks what the closed field reads as, andcountriesnarrows the list.
- It hands back the whole
Improvements
- The country sheet is configured in one place.
AppPhoneInputcarried its ownSearchPickerSheetsetup — the flag leading each row, the calling code trailing it, the ranked search that makesPTfind Portugal rather than the first name containing those letters. That configuration now lives inAppCountryPicker.show, and the phone field opens it, so a standalone country field and a phone prefix cannot drift apart.phone-inputgainscountry-pickeras a dependency; the search sheet still arrives with it.
-
2.7.031 Jul 2026Release notes
Open source →Features
AppAudioPlayer(moarch create widget audio-player) — an audio player over just_audio that a screen configures rather than wires. It owns theAudioPlayer, loads the source and disposes both. OneAppAudioSourcecovers url, asset and file.- Every part is a switch, so the same widget is a podcast screen and a
voice-note bubble:
showControls,showSkip,showProgress,allowScrub,showTimes,showRemainingandshowSpeedare independent, andAppAudioPlayerStyle.compactis the one-row arrangement. - The skip buttons take durations, not a fixed 15/30 — the number is drawn inside the arrow, so any interval works without an icon per value.
- Buffered progress rides in the bar's secondary track; a scrub is not dragged
back by the position stream mid-drag; a finished clip restarts on the next
tap rather than sitting at the end; and
onCompletedfires once per play-through rather than on every frame the player sits incompleted.
- Every part is a switch, so the same widget is a podcast screen and a
voice-note bubble:
AppDragSection(moarch create widget drag-section) — a section whose children drag into a new order, vertical or horizontal, with no dependency.- It reports the move rather than owning the list, so the order can live
in a notifier, in storage or on a server without the widget holding a
second copy of it.
AppDragSection.reorderdoes the remove-and-insert. - Each item declares its own size —
AppDragSize.small/medium/largeoff a sharedAppDragSizes, or an exactextent— and whether it can be moved. - A pinned item is a wall, not merely un-draggable: it carries no drag listener at all, and nothing can be dropped past it, so an "add" tile keeps the last slot however the rest are shuffled.
onReorderarrives already corrected for theReorderableListViewoff-by-one and for any pinned item in the way.- A long press starts the drag, because an immediate listener over the whole
item fights the scroll;
AppDragTrigger.handleputs a grip on the trailing edge instead.
- It reports the move rather than owning the list, so the order can live
in a notifier, in storage or on a server without the widget holding a
second copy of it.
AppTable(moarch create widget table) — rows and columns sized for a phone, with no dependency.- Columns are fixed (
width) or flexible (flex), and a flexible one never squeezes below itsminWidth. Past the point where the minimums no longer fit, the table pans sideways rather than crushing the columns. AppTableColumn.numericright-aligns and switches on tabular figures.- Rows take
onTap,selectedand a colour of their own;striped,showRowDividers,showColumnDividers,showBorderanddensitydecide the rest. Cells are strings, orwidgetsfor a chip or an avatar. - It deliberately owns no vertical scroll — a table that scrolls
vertically cannot sit in a page that also does. Put it in
AppSingleScrollViewor aListView.
- Columns are fixed (
AppCountryPicker(moarch create widget country-picker) — the 238-countryAppCountrytable as a field of its own, validating like the rest of the family, or asAppCountryPicker.show(context)from anywhere that is not a form.- It hands back the whole
AppCountryrather than a code, since the caller usually wants the dial code or the flag too.displaypicks what the closed field reads as, andcountriesnarrows the list.
- It hands back the whole
Improvements
- The country sheet is configured in one place.
AppPhoneInputcarried its ownSearchPickerSheetsetup — the flag leading each row, the calling code trailing it, the ranked search that makesPTfind Portugal rather than the first name containing those letters. That configuration now lives inAppCountryPicker.show, and the phone field opens it, so a standalone country field and a phone prefix cannot drift apart.phone-inputgainscountry-pickeras a dependency; the search sheet still arrives with it.
-
2.6.031 Jul 2026Release notes
Open source →Features
AppCalendar(moarch create widget calendar) — the inline month grid, for when the month itself is the content rather than one answer in a form.AppDateInputstill opens the platform picker; this is its sibling for agendas, booking screens and streaks. A wrapper over table_calendar that keeps its parameters out of your screens: colors come fromAppInputVariantlike the rest of the family, and the package is added topubspec.yamlfor you.eventsis re-keyed to the day each entry falls on. TwoDateTimes in one day are not equal, which is the usual reason a marker never appears — so you can pass the instants your data already carries, and two appointments at 09:00 and 14:00 count as two dots on one day rather than missing the grid.onMonthChangedreports the month's own bounds, not the six weeks drawn around it — the range to fetch events for. For the two-week and week formats it reports their own span.canChangeFormatoffers the month/2-week/week toggle, and only then is a vertical swipe live; without it a swipe means one thing.- No
onSelectedmakes it a read-only display, andselectableDaygreys out the days that refuse a tap. - It lives in its own
lib/shared/widgets/calendar/folder rather than alongside the fields.
AppActionSheet(moarch create widget action-sheet) — the sheet behind a three-dot button or a long press. Material rows on Android and the iOS grouped cards elsewhere, off the same platform splitAppDateInputuses for its pickers; either shape can be forced.- Rows resolve to a value, so
show<T>hands back what was picked andnullwhen it was dismissed — one honest "the user backed out" branch. - A row's
onTapruns after the sheet has closed, rather than while it is closing, where a handler that pushes a route fights the navigator for it. AppSheetAction.destructivedraws in the theme's error color. It confirms nothing on its own — pair it withAppConfirmDialogwhen the answer should be deliberate.AppDialogsandAppBottomModals, so it costs the project no GoRouter.
- Rows resolve to a value, so
Fixes
moarch create model --emptygenerated a factory that could not compile. It patches<model>_entity.dart, whose class is<Model>Entity, but named the factory after the model alone —factory LoginResponse.empty() => LoginResponse(...)insideclass LoginResponseEntity. The guard that was meant to stop a second run looked for that same wrong name, so it never matched and every re-run stacked another broken factory into the file.- A field whose type carries a comma was silently dropped from
.empty()and fromcopyWith. The type was matched with a character class holding neither a comma nor a space, soMap<String, dynamic> meta;was not a field as far as the parser was concerned — and the factory it built came out missing a required argument. Types are now read up to the last identifier on the line and then validated, which also ends the false positives that class allowed:return value;in a method body was being read as a field namedvalueof typereturn, andString get title;as a field namedtitle. - An entity file declaring a second class had the two spliced together. The
parser took a class name and ignored it, reading every field in the file, so
AddressEntity's fields turned up inUserEntity'scopyWith. It now scopes to the named class's body — and so does the injection:create entity-copysappendedcopyWithand the==/hashCodepair at the file's last closing brace, landing them on whichever class was written last, after stripping the existing equality members from every class in the file. create empty-factoriesreported replacements it had not made. Its pattern only matches an arrow-bodied.empty(), so a hand-written block-bodied one fell throughreplaceFirstunchanged while the log claimed it had been replaced. It now leaves that factory alone and says so, and a factory already matching what would be written is reported as skipped — which is what theSkipped :line in the summary always claimed to count and never did.
-
2.5.431 Jul 2026 -
2.5.330 Jul 2026 -
2.5.230 Jul 2026 -
2.5.130 Jul 2026Release notes
Open source →Features
AppAsyncView(moarch create widget async-view, generated byinit) — takes oneAsyncValueand draws the four states it can be in: a shimmered shape while the first load runs,ErrorViewwith a retry,EmptyViewwhen the screen says its data counts as empty, and your body when there is something to show. A reload over existing data leaves that data on screen rather than replacing a list mid-read with a spinner, and an error carrying no message of its own shows no detail instead of a stringified exception.ref.listenAction(...)(moarch create widget action-listener, generated byinit) — surfaces the one-shoterror/successfields a generated state already carries as anAppToast. PassonError/onSuccessto navigate or log instead; providing one replaces the toast for that outcome rather than adding to it, and a single action only ever reports one of the two.- The generated feature view is built on both.
moarch create featureused to write a.when(...)mapping by hand and leave// SHOW UI ERRORand// SHOW UI SUCCESSas comments in every feature. It now wires the two widgets up, passes its own body as the skeleton shape, and offers the retryErrorViewdraws a button for. It also writes both widgets if the project predates them, so the view it generates always compiles. AppMultiSelectInput(moarch create widget multi-select) —AppDropdownInput's plural: the same id/label entity list, any number selected, ticked in the search sheet with a per-row checkbox and a Done button. Shows its picks as removable chips, as labels, or as "3 selected"; enforcesrequired,minSelectedandmaxSelected, and stops the unticked rows at the ceiling rather than letting the form refuse the pick afterwards.SearchPickerSheet.showMulti(...)— the multi-select half of the sheet the dropdown and the country picker already open. It works on its own copy of the selection, so a dismissed sheet changes nothing and an empty result is a deliberate "none of them".AppDateRangeInput(moarch create widget date-range-input) — a read-only field holding a start and an end date, with amaxDaysrule the picker itself cannot express. It holds aDateTimeRangerather than the text of one.AppFilePickerField(moarch create widget file-input) — an attachment field: an area that opens whichever picker the app already uses, and a row per file with a thumbnail, a readable size and a remove button. It imports no picker package, so it costs the project no dependency it had not already chosen.AppRating(moarch create widget rating) — stars both ways round: tappable withonChanged, a read-only score without it. Halves come from tapping the left half of a star, and a display-only rating stays out ofForm.validate().AppTabs/AppTabBar(moarch create widget tabs) —AppTabsowns the controller and puts the views under the bar, replacing it when the tab count changes;AppTabBaris thePreferredSizeWidgethalf that drops intoAppAppBar'sbottomslot. Underline or pill indicator.AppDrawer(moarch create widget drawer) — the side menu, readingAppBottomNav's destination list, with header and footer slots. It closes itself after a pick, and does nothing when the same widget is pinned beside the content instead.AppNavRailandAppAdaptiveNav(moarch create widget nav-rail) — the vertical navigation a tablet shows instead of a bottom bar, and the scaffold that picks between them off the 600dp short-side breakpoint. All three nav widgets read oneAppNavDestinationlist.AppFab(moarch create widget fab) — the screen's floating action, circular or extended off one parameter, wearingAppButtonVariant/AppButtonTyperather than a vocabulary of its own.isLoadingswaps the icon for a spinner without resizing the button, andheroTagis exposed for the two-FABs-on-one-screen case.AppTimeline(moarch create widget timeline) — a vertical sequence of events joined by a connector, with done/current/pending/failed nodes.AppTimeline.entryBuilderhands you one row for a lazy list.AppCarousel(moarch create widget carousel) — swipeable pages with stretching dots, optional peek and auto-advance. The timer stops for good on the first swipe and never starts when the platform asks for reduced motion;AppCarouselDotsis usable on its own.
Fixes
moarch initnever wrote its ownlib/main.dart.flutter createleaves one behind and generated files are never clobbered, so on the documented quick start (flutter create→moarch init) the counter demo survived and the scaffold's main.dart — the one that installsProviderScopeand initialises the selected services — was silently skipped. Every scaffolded app was missing its provider root. The counter demo is now replaced, matched on the two private names only that template declares; a main.dart you wrote is still left alone, and init says so instead of passing over it in silence. The counterwidget_test.dartthat pumped it is replaced on the same terms, soflutter testpasses on a fresh project.file_pickerresolved to 3.0.4 (2021) whenever the media service was selected, and 3.0.4 predates AGP'snamespacerequirement — so the Android build failed with "Namespace not specified" before the app could run. The entry was unversioned, and pub is free to resolve backwards:file_picker11 wantswin32 ^5,flutter_secure_storage_windowswantswin32 ^6, and walkingfile_pickerback to 3.0.4 settled that Windows-only conflict. It now carries a^11.0.0floor, andMediaServicecalls the staticFilePicker.pickFilesthat version moved to.- Three widgets used null-aware elements (
?header), which need the project's pubspec to ask for Dart 3.8+ — not merely a recent SDK to be installed — so they failed to compile in a project scaffolded a while ago. Rewritten to constructs with no language-version floor, and a test now fails if a template reaches for one again.
Improvements
AppToastwas redrawn. It was a greysurfaceContainerHighestbar with a 4px accent stripe and an icon beside it — a Material 2 snackbar with a decoration. It is now a card: a surface tinted 7% with the status color, a status-colored outline, a soft shadow, and the icon in a tonal chip matchingAppLeadingIcon's. The outline is what the old one could not have — aSnackBartakes a color and a shape but not a border — so the toast now draws its own card inside a transparent, unelevated SnackBar. It also gains atitleover the detail line, an optional close button, awarning/infohelper to go withsuccess/error,AppToast.dismiss, a 480px ceiling so it stays a card rather than a banner on a tablet, and sideways swipe-to-dismiss. In dark themes it now sits onsurfaceContainerHighest— an overlay has to be lighter than the page it covers, and the old bar was darker than the content behind it.AppButton'shintmoved inside the button, centered under the label, in the button's own foreground color; the button grows to fit it. It used to be a left-aligned line floating above the button, which read as a caption for whatever was above it rather than as part of the action.- The design-system preview covers
AppPhoneInputandAppAsyncView— the phone field has been in the kit since 2.4.0 without a preview, and the async view's four states are steppable in it. A new test fails if a widget joins the catalog without either a preview section or an explicit, reasoned exemption, so the screen can no longer fall behind the kit unnoticed. moarch create featurerecords what it writes intoshared/widgets/in.moarch.yaml, somoarch updatecan tell those files apart from ones you have since edited.
-
2.5.030 Jul 2026Release notes
Open source →Features
AppAsyncView(moarch create widget async-view, generated byinit) — takes oneAsyncValueand draws the four states it can be in: a shimmered shape while the first load runs,ErrorViewwith a retry,EmptyViewwhen the screen says its data counts as empty, and your body when there is something to show. A reload over existing data leaves that data on screen rather than replacing a list mid-read with a spinner, and an error carrying no message of its own shows no detail instead of a stringified exception.ref.listenAction(...)(moarch create widget action-listener, generated byinit) — surfaces the one-shoterror/successfields a generated state already carries as anAppToast. PassonError/onSuccessto navigate or log instead; providing one replaces the toast for that outcome rather than adding to it, and a single action only ever reports one of the two.- The generated feature view is built on both.
moarch create featureused to write a.when(...)mapping by hand and leave// SHOW UI ERRORand// SHOW UI SUCCESSas comments in every feature. It now wires the two widgets up, passes its own body as the skeleton shape, and offers the retryErrorViewdraws a button for. It also writes both widgets if the project predates them, so the view it generates always compiles. AppMultiSelectInput(moarch create widget multi-select) —AppDropdownInput's plural: the same id/label entity list, any number selected, ticked in the search sheet with a per-row checkbox and a Done button. Shows its picks as removable chips, as labels, or as "3 selected"; enforcesrequired,minSelectedandmaxSelected, and stops the unticked rows at the ceiling rather than letting the form refuse the pick afterwards.SearchPickerSheet.showMulti(...)— the multi-select half of the sheet the dropdown and the country picker already open. It works on its own copy of the selection, so a dismissed sheet changes nothing and an empty result is a deliberate "none of them".AppDateRangeInput(moarch create widget date-range-input) — a read-only field holding a start and an end date, with amaxDaysrule the picker itself cannot express. It holds aDateTimeRangerather than the text of one.AppFilePickerField(moarch create widget file-input) — an attachment field: an area that opens whichever picker the app already uses, and a row per file with a thumbnail, a readable size and a remove button. It imports no picker package, so it costs the project no dependency it had not already chosen.AppRating(moarch create widget rating) — stars both ways round: tappable withonChanged, a read-only score without it. Halves come from tapping the left half of a star, and a display-only rating stays out ofForm.validate().AppTabs/AppTabBar(moarch create widget tabs) —AppTabsowns the controller and puts the views under the bar, replacing it when the tab count changes;AppTabBaris thePreferredSizeWidgethalf that drops intoAppAppBar'sbottomslot. Underline or pill indicator.AppDrawer(moarch create widget drawer) — the side menu, readingAppBottomNav's destination list, with header and footer slots. It closes itself after a pick, and does nothing when the same widget is pinned beside the content instead.AppNavRailandAppAdaptiveNav(moarch create widget nav-rail) — the vertical navigation a tablet shows instead of a bottom bar, and the scaffold that picks between them off the 600dp short-side breakpoint. All three nav widgets read oneAppNavDestinationlist.AppFab(moarch create widget fab) — the screen's floating action, circular or extended off one parameter, wearingAppButtonVariant/AppButtonTyperather than a vocabulary of its own.isLoadingswaps the icon for a spinner without resizing the button, andheroTagis exposed for the two-FABs-on-one-screen case.AppTimeline(moarch create widget timeline) — a vertical sequence of events joined by a connector, with done/current/pending/failed nodes.AppTimeline.entryBuilderhands you one row for a lazy list.AppCarousel(moarch create widget carousel) — swipeable pages with stretching dots, optional peek and auto-advance. The timer stops for good on the first swipe and never starts when the platform asks for reduced motion;AppCarouselDotsis usable on its own.
Improvements
- The design-system preview covers
AppPhoneInputandAppAsyncView— the phone field has been in the kit since 2.4.0 without a preview, and the async view's four states are steppable in it. A new test fails if a widget joins the catalog without either a preview section or an explicit, reasoned exemption, so the screen can no longer fall behind the kit unnoticed. moarch create featurerecords what it writes intoshared/widgets/in.moarch.yaml, somoarch updatecan tell those files apart from ones you have since edited.
-
2.4.030 Jul 2026Release notes
Open source →Features
AppPhoneInput(moarch create widget phone-input) — a phone field that masks what is typed for the country it is set to, with a searchable country picker in its prefix. The field holds only the national number, so the calling code cannot be typed twice or deleted; read the joined-up value fromAppPhoneNumber.e164. Changing country re-masks the existing digits rather than clearing them.AppCountry(moarch create widget country) — a table of 238 countries with ISO code, calling code and every mask their numbering plan allows. Plans with more than one shape are kept as a list, so the mask widens as the number grows (Hungary 8–9 digits, Germany 10–13), and validation holds a number to those exact lengths instead of a 7-to-15 range. Flags are derived from the ISO code, so no assets ship with it.SearchPickerSheet(moarch create widget search-sheet) — a bottom sheet that picks one row out of a long list, with a search field above it. Opens scrolled to the current selection.AppDropdownInput— swaps the menu for that sheet once the list passesAppInputConfig.searchableThreshold(30), whichsearchable: true/falseoverrules per field. Both forms now validate:required: trueis enforced byForm.validate()rather than only marking the label, andvalidator/autovalidateModework as they do onAppInput. Also gainsonSelected(the picked entity, not just its id),onCleared(which puts a clear button in the field), and the sheet'sleadingOf,trailingLabelOf,filterandemptyLabel.moarch update— refreshes generated UI-kit widgets against the current templates. Files you never touched are refreshed automatically; files you edited are listed, diffed and left alone unless you pass--force..moarch.yaml— a manifest written byinitandcreate widgetrecording the moarch version, the selected stack and a hash of every generated file. It is what letsupdatetell an untouched file from an edited one.moarch doctor --fix— applies the fixes that don't need a decision.
Fixes
AppDateInput/AppTimeInputshowed nothing without a caller-supplied controller: every value,initialValueand each picked one alike, was written only towidget.controller?.text. They now own a controller when none is passed (and dispose it), so the simplest possible usage displays its value. A controller that already holds text is no longer overwritten byinitialValueeither — the caller's value wins, as it does onAppInput.required: trueonAppDateInputandAppTimeInputonly drew an asterisk;Form.validate()passed an empty field. Both now validate, and take avalidator/autovalidateModelike the rest of the family.AppLoadingActionOverlaystarted its message timers only on a false-to-true change, so a screen that mounted with a request already in flight showed a bare spinner forever. They now start ininitStatetoo.AppSegmentedandAppChoiceChipdrew their selected foreground incolorScheme.surface, which is only the right answer in a light theme. Both now use the newAppInputStyle.onAccentOf, whichAppCheckboxandAppSwitchshare.
Improvements
-
Selection controls validate.
AppCheckboxLabel(required: true)is the "accept the terms" checkbox aFormcan enforce, andAppRadioGroup(required: true)refuses to validate until one option is chosen. Both render the error under the control through the newSelectionFormField, which is exposed for wrapping any control of your own. -
AppSegmentedandAppRadioGroupassert that the current selection is actually one of the options, instead of silently rendering with nothing highlighted. -
AppCheckboxLabel,AppRadioGroup,AppSegmentedandAppChoiceChipall take a null callback to disable themselves, matchingAppCheckbox,AppSwitch,AppSliderandAppStepper.AppDateInputandAppTimeInputgainenabledalongside their existingreadOnly. -
AppStepper's − and + carry tooltips and semantics, and meet the 48px minimum tap target. -
moarch doctornow checks what the scaffold actually depends on: whetherbuild_runnerhas generatedapp_env.g.dart, whether both localization approaches ended up installed, whether every generated widget's dependencies and pub packages are present, and whether router-dependent widgets have a router. Findings carry hints, and say which are fixable.
-
2.3.029 Jul 2026Release notes
Open source →Features
AppPhoneInput(moarch create widget phone-input) — a phone field that masks what is typed for the country it is set to, with a searchable country picker in its prefix. The field holds only the national number, so the calling code cannot be typed twice or deleted; read the joined-up value fromAppPhoneNumber.e164. Changing country re-masks the existing digits rather than clearing them.AppCountry(moarch create widget country) — a table of 238 countries with ISO code, calling code and every mask their numbering plan allows. Plans with more than one shape are kept as a list, so the mask widens as the number grows (Hungary 8–9 digits, Germany 10–13), and validation holds a number to those exact lengths instead of a 7-to-15 range. Flags are derived from the ISO code, so no assets ship with it.SearchPickerSheet(moarch create widget search-sheet) — a bottom sheet that picks one row out of a long list, with a search field above it. Opens scrolled to the current selection.AppDropdownInput— swaps the menu for that sheet once the list passesAppInputConfig.searchableThreshold(30), whichsearchable: true/falseoverrules per field. Both forms now validate:required: trueis enforced byForm.validate()rather than only marking the label, andvalidator/autovalidateModework as they do onAppInput. Also gainsonSelected(the picked entity, not just its id),onCleared(which puts a clear button in the field), and the sheet'sleadingOf,trailingLabelOf,filterandemptyLabel.moarch update— refreshes generated UI-kit widgets against the current templates. Files you never touched are refreshed automatically; files you edited are listed, diffed and left alone unless you pass--force..moarch.yaml— a manifest written byinitandcreate widgetrecording the moarch version, the selected stack and a hash of every generated file. It is what letsupdatetell an untouched file from an edited one.moarch doctor --fix— applies the fixes that don't need a decision.
Fixes
AppDateInput/AppTimeInputshowed nothing without a caller-supplied controller: every value,initialValueand each picked one alike, was written only towidget.controller?.text. They now own a controller when none is passed (and dispose it), so the simplest possible usage displays its value. A controller that already holds text is no longer overwritten byinitialValueeither — the caller's value wins, as it does onAppInput.required: trueonAppDateInputandAppTimeInputonly drew an asterisk;Form.validate()passed an empty field. Both now validate, and take avalidator/autovalidateModelike the rest of the family.AppLoadingActionOverlaystarted its message timers only on a false-to-true change, so a screen that mounted with a request already in flight showed a bare spinner forever. They now start ininitStatetoo.AppSegmentedandAppChoiceChipdrew their selected foreground incolorScheme.surface, which is only the right answer in a light theme. Both now use the newAppInputStyle.onAccentOf, whichAppCheckboxandAppSwitchshare.
Improvements
-
Selection controls validate.
AppCheckboxLabel(required: true)is the "accept the terms" checkbox aFormcan enforce, andAppRadioGroup(required: true)refuses to validate until one option is chosen. Both render the error under the control through the newSelectionFormField, which is exposed for wrapping any control of your own. -
AppSegmentedandAppRadioGroupassert that the current selection is actually one of the options, instead of silently rendering with nothing highlighted. -
AppCheckboxLabel,AppRadioGroup,AppSegmentedandAppChoiceChipall take a null callback to disable themselves, matchingAppCheckbox,AppSwitch,AppSliderandAppStepper.AppDateInputandAppTimeInputgainenabledalongside their existingreadOnly. -
AppStepper's − and + carry tooltips and semantics, and meet the 48px minimum tap target. -
moarch doctornow checks what the scaffold actually depends on: whetherbuild_runnerhas generatedapp_env.g.dart, whether both localization approaches ended up installed, whether every generated widget's dependencies and pub packages are present, and whether router-dependent widgets have a router. Findings carry hints, and say which are fixable.
-
2.2.229 Jul 2026 -
2.2.128 Jul 2026 -
2.2.028 Jul 2026 -
2.1.127 Jul 2026 -
2.1.027 Jul 2026 -
2.0.324 Jul 2026 -
2.0.224 Jul 2026 -
2.0.024 Jul 2026 -
1.8.1023 Jul 2026 -
1.8.921 Jul 2026 -
1.8.820 Jul 2026 -
1.8.719 Jul 2026 -
1.8.619 Jul 2026 -
1.8.517 Jul 2026 -
1.8.417 Jul 2026 -
1.8.317 Jul 2026