flutter_riverpod_clean_generator 0.1.1 copy "flutter_riverpod_clean_generator: ^0.1.1" to clipboard
flutter_riverpod_clean_generator: ^0.1.1 copied to clipboard

A code generator that scaffolds a feature-first Clean Architecture project structure with Riverpod (Notifier/AsyncNotifier) into a Flutter project.

flutter_riverpod_clean_generator #

A code generator - not a runtime library - that scaffolds a feature-first Clean Architecture project structure into a Flutter app, using Riverpod Notifier/AsyncNotifier for state management.

Add it to a Flutter project, run it once (or once per feature), and it writes core/ and features/<name>/ folders full of production-ready Dart source directly into your lib/. Nothing from this package ships in your app - once generation is done, your project depends only on flutter_riverpod, go_router, dio, and shared_preferences.

Inspired by clean_gen_tool_plus, but built around Riverpod instead of Bloc/Cubit, and safe by default: it never overwrites a file that already exists unless you explicitly ask it to, and it never touches your pubspec.yaml.

Install #

dev_dependencies:
  flutter_riverpod_clean_generator:
    git:
      url: <this repository>

(Or path: ../flutter_riverpod_clean_generator while developing locally, as example/pubspec.yaml in this repo does.) It's a dev_dependency - it's a build-time tool, nothing it exports is imported by your shipped app.

Quick start #

Create lib/gen_tool.dart in your Flutter project:

import 'package:flutter_riverpod_clean_generator/flutter_riverpod_clean_generator.dart';

void main() async {
  await CleanArchitectureGenerator.generate();
}

Run it:

dart run lib/gen_tool.dart

With no arguments, this walks you through an interactive wizard: whether to generate core/, which features to add and what kind each one is, whether to overwrite files that already exist, and the output directory.

Non-interactive / scriptable #

Pass GeneratorOptions directly - this is also what every automated test in this repo uses:

import 'package:flutter_riverpod_clean_generator/flutter_riverpod_clean_generator.dart';

void main() async {
  await CleanArchitectureGenerator.generate(
    options: GeneratorOptions(
      includeCore: true,
      features: [
        FeatureConfig(name: 'auth', kind: FeatureKind.form),
        FeatureConfig(name: 'dashboard', kind: FeatureKind.list),
      ],
    ),
  );
}

CLI flags #

For flag-driven usage, forward args into GeneratorOptions.fromArguments:

void main(List<String> args) async {
  await CleanArchitectureGenerator.generate(
    options: args.isEmpty ? null : GeneratorOptions.fromArguments(args),
  );
}
dart run lib/gen_tool.dart --core --feature=auth:form --feature=dashboard:list
dart run lib/gen_tool.dart --feature=profile --dry-run   # preview, writes nothing
dart run lib/gen_tool.dart --core --force                # overwrite existing files
dart run lib/gen_tool.dart --help
Flag Meaning
--core Generate the core/ layer
--feature=name[:kind] Add a feature; repeatable. kind is entity, list, or form (default entity)
--output=<dir> Output directory relative to the project root (default lib)
--force, -f Overwrite files that already exist (default: skip them)
--dry-run Compute and print the plan; write nothing
--help, -h Print usage

Tip: run dart format . in your project after generating - like most generators, this one doesn't reformat on write. (Deliberately: this package's own tests use dart_style to assert every template is valid Dart, but dart_style stays a dev dependency here - pulling it in as a runtime dependency would drag in analyzer, which needs a newer meta than the Flutter SDK pins, making the package unresolvable inside any Flutter project.)

Feature kinds #

Kind Use for Presentation state
entity A single async resource (a profile, a settings blob) AsyncNotifier's own AsyncValue<Entity> - no custom state class
list A paginated collection (a product list, a feed) AsyncNotifier<PaginatedState> with items/page/hasMore/isLoadingMore
form A multi-field submission flow (login, sign-up, contact) Notifier<FormState> with a submission-status enum

Every kind produces the same layered shape (data/domain/presentation); only the state, notifier, use case, page, and widget differ.

Generated project structure #

lib/
├── core/
│   ├── constants/     app_constants.dart, api_constants.dart
│   ├── errors/        failure.dart, result.dart, app_exceptions.dart, exception_mapper.dart
│   ├── network/       api_client.dart (Dio), network_info.dart (connectivity_plus)
│   ├── routing/       app_router.dart (go_router) - add your feature routes here by hand
│   ├── theme/         app_colors.dart, app_text_styles.dart, app_theme.dart
│   ├── localization/  locale_provider.dart
│   ├── storage/       local_storage_service.dart (shared_preferences)
│   ├── utils/         usecase.dart (the shared UseCase<ResultType, Params> contract)
│   └── di/            core_providers.dart - cross-cutting infra only (Dio, storage, ...)
└── features/
    └── <feature_name>/
        ├── data/
        │   ├── datasources/   <feature>_remote_data_source.dart, <feature>_local_data_source.dart
        │   ├── models/        <feature>_model.dart (fromJson/toJson/toEntity)
        │   └── repositories/  <feature>_repository_impl.dart
        ├── domain/                                  (pure Dart - no Flutter or Riverpod import)
        │   ├── entities/    <feature>_entity.dart
        │   ├── repositories/<feature>_repository.dart
        │   └── usecases/    get_<feature>_usecase.dart / submit_<feature>_usecase.dart / ...
        └── presentation/
            ├── providers/  <feature>_providers.dart (DI wiring), <feature>_notifier.dart, <feature>_state.dart*
            ├── pages/      <feature>_page.dart
            └── widgets/    <feature>_*_widget.dart

* only for list/form kinds.

See example/ for the real output of running this generator - example/lib/core and example/lib/features/{auth,dashboard} are not hand-written, they're what dart run lib/gen_tool.dart actually produced.

Design decisions #

  • Domain layer is pure Dart. entities/, repositories/ (interfaces), and usecases/ never import flutter or flutter_riverpod. Result/ Failure (core/errors/) are a small Dart 3 sealed-class pair, not dartz/fpdart.
  • No @riverpod code generation. Every Notifier/AsyncNotifier is hand-written, wired via a plain NotifierProvider/AsyncNotifierProvider. Generated code compiles and runs immediately - no build_runner step. Dependencies are resolved with ref.watch(...) inside build(), never through a constructor, since Riverpod recreates notifiers on rebuild.
  • go_router, not auto_route - consistent with "no forced build step," and it's the Flutter-team-endorsed router.
  • Never touches your pubspec.yaml. After generating, the console output lists the packages the generated code needs; you add them yourself. (The reference tool this was inspired by regenerates the whole pubspec.yaml unconditionally - this package deliberately never does that.)
  • Safe by default. Every file is checked against the filesystem before writing: if it exists, it's skipped (not overwritten) unless you pass overwrite: true / --force. A GenerationReport tells you exactly what was created, skipped, or overwritten.
  • core/routing/app_router.dart is meant to be hand-edited afterward - add your feature's GoRoutes there yourself once its pages exist. It's generated once and never depends on which features exist yet, so it's never regenerated out from under your edits unless you explicitly pass overwrite: true.

How it's built (for contributors) #

  • lib/src/templates/ - one small pure function per generated file (String buildXFile(...)), no hardcoded strings mixed into orchestration logic. Every template's output is asserted to be syntactically valid Dart in test/ via package:dart_style's DartFormatter.
  • lib/src/engine/ - core_generator.dart/feature_generator.dart turn options into a list of PlannedFiles (no I/O beyond existence checks); file_writer.dart decides create/skip/overwrite and performs the actual writes, recording per-file failures without aborting the run.
  • lib/src/config/ - GeneratorOptions, FeatureConfig/FeatureKind, GenerationPlan/GenerationReport, GeneratorException.
  • lib/src/interaction/ - Prompter (real StdinPrompter + a fake queued-answer implementation used in tests) and console reporting.

Testing #

dart pub get
dart analyze
dart test

39 tests cover: case-conversion utilities, CLI argument parsing, the file-writer's create/skip/overwrite/partial-failure behavior, every core and per-FeatureKind template (path layout + Dart-syntax validity), and full end-to-end runs (including the interactive wizard via a fake prompter, --dry-run, and rejecting a non-Flutter directory).

example/ is a real Flutter app whose lib/core and lib/features/{auth,dashboard} were produced by actually running this generator; flutter analyze and flutter test test/generated_smoke_test.dart both pass against that output.

Credits #

Created by odai0962.

License #

MIT - see LICENSE.

1
likes
160
points
150
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A code generator that scaffolds a feature-first Clean Architecture project structure with Riverpod (Notifier/AsyncNotifier) into a Flutter project.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

args, path

More

Packages that depend on flutter_riverpod_clean_generator