You are creating a complete feature module for a scalable Flutter project - Cubit for state, flutter_injections for DI, one folder per feature under lib/modules/.
Input
- Module name: from the request, or from the skill argument (e.g., "payment", "order", "profile"). If no name was given, ask before creating any file — it drives every directory, file and class name below, and renaming afterwards touches all of them.
- If a second argument is given, it's the package path (e.g., "../my_app/lib")
- If no path given, use the current package's
lib/directory
Module Structure to Create
Create ALL the following files for module <module_name>:
modules/<module_name>/
├── <module_name>_module.dart ← Module entry + DI registration
└── presentation/
├── <module_name>_controller.dart ← Cubit (state machine)
├── <module_name>_status.dart ← State classes (sealed/abstract)
├── <module_name>_screen.dart ← Main UI screen
└── widgets/ ← Module-specific widgets (if needed)
And in core (if new domain is needed):
domain/
├── entities/<module_name>.dart ← Immutable entity (Equatable)
├── repositories/<module_name>_repository.dart ← Abstract interface
└── usecases/<module_name>/
└── get_<module_name>_usecase.dart
data/
├── models/<module_name>_model.dart ← API model with .toEntity()
├── datasources/<module_name>_datasource.dart ← Dio HTTP calls
└── repositories/<module_name>_repository_impl.dart
File Templates
1. Entity (domain/entities/.dart)
import 'package:equatable/equatable.dart';
class ModuleName extends Equatable {
final String id;
// add fields
const ModuleName({required this.id});
@override
List<Object?> get props => [id];
}
2. Repository interface (domain/repositories/_repository.dart)
import '../entities/<module_name>.dart';
abstract class <ModuleName>Repository {
Future<List<ModuleName>> getAll();
}
3. Usecase (domain/usecases//get__usecase.dart)
import '../../repositories/<module_name>_repository.dart';
import '../../entities/<module_name>.dart';
class Get<ModuleName>Usecase {
final <ModuleName>Repository _repository;
Get<ModuleName>Usecase(this._repository);
Future<List<ModuleName>> call() => _repository.getAll();
}
4. API Model (data/models/_model.dart)
import '../../domain/entities/<module_name>.dart';
class <ModuleName>Model {
final String id;
// add fields from API
<ModuleName>Model({required this.id});
factory <ModuleName>Model.fromJson(Map<String, dynamic> json) =>
<ModuleName>Model(id: json['id']);
<ModuleName> toEntity() => <ModuleName>(id: id);
}
5. Datasource (data/datasources/_datasource.dart)
import 'package:dio/dio.dart';
import '../models/<module_name>_model.dart';
class <ModuleName>Datasource {
final Dio _dio;
<ModuleName>Datasource(this._dio);
Future<List<<ModuleName>Model>> getAll() async {
final response = await _dio.get('/<module_name>s');
return (response.data as List)
.map((e) => <ModuleName>Model.fromJson(e))
.toList();
}
}
6. Repository impl (data/repositories/_repository_impl.dart)
import '../../domain/entities/<module_name>.dart';
import '../../domain/repositories/<module_name>_repository.dart';
import '../datasources/<module_name>_datasource.dart';
class <ModuleName>RepositoryImpl implements <ModuleName>Repository {
final <ModuleName>Datasource _dataSource;
<ModuleName>RepositoryImpl(this._dataSource);
@override
Future<List<ModuleName>> getAll() async {
final result = await _dataSource.getAll();
return result.map((e) => e.toEntity()).toList();
}
}
7. Status (presentation/_status.dart)
abstract class <ModuleName>Status {
const <ModuleName>Status();
}
class <ModuleName>Initial extends <ModuleName>Status {
const <ModuleName>Initial();
}
class <ModuleName>Loading extends <ModuleName>Status {
const <ModuleName>Loading();
}
class <ModuleName>Success extends <ModuleName>Status {
final List<<Entity>> items;
const <ModuleName>Success(this.items);
}
class <ModuleName>Failure extends <ModuleName>Status {
final String message;
const <ModuleName>Failure(this.message);
}
8. Controller (presentation/_controller.dart)
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_core/domain/usecases/<module_name>/get_<module_name>_usecase.dart';
import '<module_name>_status.dart';
class <ModuleName>Controller extends Cubit<<ModuleName>Status> {
final Get<ModuleName>Usecase _get<ModuleName>;
<ModuleName>Controller(this._get<ModuleName>) : super(const <ModuleName>Initial());
Future<void> load() async {
emit(const <ModuleName>Loading());
try {
final items = await _get<ModuleName>.call();
emit(<ModuleName>Success(items));
} catch (e) {
emit(<ModuleName>Failure(e.toString()));
}
}
}
9. Screen (presentation/_screen.dart)
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '<module_name>_controller.dart';
import '<module_name>_status.dart';
class <ModuleName>Screen extends StatefulWidget {
const <ModuleName>Screen({super.key});
@override
State<<ModuleName>Screen> createState() => _<ModuleName>ScreenState();
}
class _<ModuleName>ScreenState extends State<<ModuleName>Screen> {
@override
void initState() {
super.initState();
context.read<<ModuleName>Controller>().load();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('<ModuleName>')),
body: BlocBuilder<<ModuleName>Controller, <ModuleName>Status>(
builder: (context, status) {
if (status is <ModuleName>Loading) {
return const Center(child: CircularProgressIndicator());
}
if (status is <ModuleName>Failure) {
return Center(child: Text(status.message));
}
if (status is <ModuleName>Success) {
return ListView.builder(
itemCount: status.items.length,
itemBuilder: (context, index) {
final item = status.items[index];
return ListTile(title: Text(item.id));
},
);
}
return const SizedBox.shrink();
},
),
);
}
}
10. Module entry + DI (_module.dart)
import 'package:flutter/material.dart';
import 'package:flutter_injections/flutter_injections.dart';
import 'package:app_core/domain/usecases/<module_name>/get_<module_name>_usecase.dart';
import 'package:app_core/domain/repositories/<module_name>_repository.dart';
import 'presentation/<module_name>_controller.dart';
import 'presentation/<module_name>_screen.dart';
class <ModuleName>Module extends FlutterModule {
const <ModuleName>Module({super.key});
@override
List<Inject<Object>> get injections => [
Inject<<ModuleName>Controller>.lazySingleton(
(i) => <ModuleName>Controller(
Get<ModuleName>Usecase(i.find<<ModuleName>Repository>()),
),
),
];
@override
Widget get child => BlocProvider(
create: (_) => FlutterInjections.get<<ModuleName>Controller>(),
child: const <ModuleName>Screen(),
);
}
Instructions
- Read the existing project structure first to understand conventions
- Create all files above substituting
<ModuleName>and<module_name>with the actual name - Register the new repository and datasource in
CoreInjectionsincore/lib/injections.dart - Add the new route to the app's
app_routes.dart - Use the same import alias convention already in the project
- Do NOT add documentation comments or unnecessary annotations
- Keep controllers lean — only state transitions in controller, logic in usecases