zuraffa 1.4.0
zuraffa: ^1.4.0 copied to clipboard
AI first Flutter Clean Architecture Framework and CLI with Result types, UseCase patterns, Dependency Injection and MCP server for building type-safe, scalable apps with AI agents.
π¦ Zuraffa #
A comprehensive Clean Architecture framework for Flutter applications with Result-based error handling, type-safe failures, and minimal boilerplate.
What is Zuraffa? #
π¦ Zuraffa (ZΓΌrafa means Giraffe in TΓΌrkΓ§e) is a modern Flutter package that implements Clean Architecture principles with a focus on developer experience and type safety. It provides a robust set of tools for building scalable, testable, and maintainable Flutter applications.
Key Features #
- β
Result Type: Type-safe error handling with
Result<T, AppFailure> - β Sealed Failures: Exhaustive pattern matching for error cases
- β UseCase Pattern: Single-shot, streaming, and background operations
- β Controller: Simple state management with automatic cleanup
- β
CLI Tool: Generate boilerplate code with
zfacommand - β MCP Server: AI/IDE integration via Model Context Protocol
- β
Cancellation: Cooperative cancellation with
CancelToken - β Fine-grained Rebuilds: Optimize performance with selective widget updates
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
zuraffa: ^1.0.0
Then run:
flutter pub get
Quick Start #
1. Generate Code with the CLI #
The easiest way to get started is using the zfa CLI:
# Activate the CLI
dart pub global activate zuraffa
# Generate a complete feature with UseCases, Repository, Controller, and View
zfa generate Product --methods=get,getList,create,update,delete --repository --vpc
# Or use the shorter alias
dart run zuraffa:zfa generate Product --methods=get,getList --repository
2. Use a Controller #
class ProductPage extends CleanView {
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends CleanViewState<ProductPage, ProductController> {
_ProductPageState() : super(ProductController(repository: getIt()));
@override
void onInitState() {
super.onInitState();
controller.loadProducts();
}
@override
Widget get view {
return Scaffold(
key: globalKey,
appBar: AppBar(title: const Text('Products')),
body: ControlledWidgetBuilder<ProductController>(
builder: (context, controller) {
if (controller.viewState.isLoading) {
return const CircularProgressIndicator();
}
return ListView.builder(
itemCount: controller.viewState.products.length,
itemBuilder: (context, index) {
final product = controller.viewState.products[index];
return ListTile(title: Text(product.name));
},
);
},
),
);
}
}
Core Concepts #
Result Type #
All operations return Result<T, AppFailure> for type-safe error handling:
final result = await getUserUseCase('user-123');
// Pattern matching with fold
result.fold(
(user) => showUser(user),
(failure) => showError(failure),
);
// Or use switch for exhaustive handling
switch (failure) {
case NotFoundFailure():
showNotFound();
case NetworkFailure():
showOfflineMessage();
case UnauthorizedFailure():
navigateToLogin();
default:
showGenericError();
}
AppFailure Hierarchy #
Zuraffa provides a sealed class hierarchy for comprehensive error handling:
sealed class AppFailure implements Exception {
final String message;
final StackTrace? stackTrace;
final Object? cause;
}
// Specific failure types
final class ServerFailure extends AppFailure { ... }
final class NetworkFailure extends AppFailure { ... }
final class ValidationFailure extends AppFailure { ... }
final class NotFoundFailure extends AppFailure { ... }
final class UnauthorizedFailure extends AppFailure { ... }
final class ForbiddenFailure extends AppFailure { ... }
final class TimeoutFailure extends AppFailure { ... }
final class CacheFailure extends AppFailure { ... }
final class ConflictFailure extends AppFailure { ... }
final class CancellationFailure extends AppFailure { ... }
final class UnknownFailure extends AppFailure { ... }
UseCase Types #
Single-shot UseCase
For operations that return once:
class GetUserUseCase extends UseCase<User, String> {
final UserRepository _repository;
GetUserUseCase(this._repository);
@override
Future<User> execute(String userId, CancelToken? cancelToken) async {
return _repository.getUser(userId);
}
}
StreamUseCase
For reactive operations that emit multiple values:
class WatchProductsUseCase extends StreamUseCase<List<Product>, NoParams> {
final ProductRepository _repository;
WatchProductsUseCase(this._repository);
@override
Stream<List<Product>> execute(NoParams params, CancelToken? cancelToken) {
return _repository.watchProducts();
}
}
BackgroundUseCase
For CPU-intensive operations on isolates:
class ProcessImageUseCase extends BackgroundUseCase<ProcessedImage, ImageParams> {
@override
BackgroundTask<ImageParams> buildTask() => _processImage;
static void _processImage(BackgroundTaskContext<ImageParams> context) {
final result = applyFilters(context.params.image);
context.sendData(result);
context.sendDone();
}
}
Controller #
Manages UI state and coordinates UseCases:
class ProductController extends Controller {
final GetProductsUseCase _getProducts;
ProductState _viewState = const ProductState();
ProductState get viewState => _viewState;
ProductController({required ProductRepository repository})
: _getProducts = GetProductsUseCase(repository);
Future<void> loadProducts() async {
_setState(_viewState.copyWith(isLoading: true));
final result = await execute(_getProducts, const NoParams());
result.fold(
(products) => _setState(_viewState.copyWith(
products: products,
isLoading: false,
)),
(failure) => _setState(_viewState.copyWith(
error: failure,
isLoading: false,
)),
);
}
void _setState(ProductState newState) {
_viewState = newState;
refreshUI();
}
}
CleanView #
Base class for views with automatic lifecycle management:
class ProductPage extends CleanView {
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends CleanViewState<ProductPage, ProductController> {
_ProductPageState() : super(ProductController(repository: getIt()));
@override
Widget get view {
return Scaffold(
key: globalKey, // Important: use globalKey on root widget
body: YourBodyWidget(),
);
}
}
CLI Tool #
Zuraffa includes a powerful CLI tool (zfa) for generating boilerplate code.
Installation #
# Global activation
dart pub global activate zuraffa
# Or run directly
dart run zuraffa:zfa
Basic Usage #
# Generate UseCases for an entity
zfa generate Product --methods=get,getList,create,update,delete --repository
# Add presentation layer (View, Presenter, Controller)
zfa generate Product --methods=get,getList --repository --vpc
# Add data layer (DataRepository + DataSource)
zfa generate Product --methods=get,getList --repository --data
# Generate everything at once
zfa generate Product --methods=get,getList,create --repository --vpc --data
# Custom UseCase
zfa generate ProcessOrder --repos=OrderRepo,PaymentRepo --params=OrderRequest --returns=OrderResult
Available Methods #
| Method | UseCase Type | Description |
|---|---|---|
get |
UseCase | Get single entity by ID |
getList |
UseCase | Get all entities |
create |
UseCase | Create new entity |
update |
UseCase | Update existing entity |
delete |
CompletableUseCase | Delete entity by ID |
watch |
StreamUseCase | Watch single entity |
watchList |
StreamUseCase | Watch all entities |
AI/JSON Integration #
# JSON output for parsing
zfa generate Product --methods=get,getList --format=json
# Read from stdin
echo '{"name":"Product","methods":["get","getList"]}' | zfa generate Product --from-stdin
# Get JSON schema for validation
zfa schema
# Dry run (preview without writing)
zfa generate Product --methods=get --dry-run --format=json
For complete CLI documentation, see CLI_GUIDE.md.
MCP Server #
Zuraffa includes an MCP (Model Context Protocol) server for seamless integration with AI-powered development environments like Claude Desktop, Cursor, and VS Code.
Running the MCP Server #
# Compile for faster startup
dart compile exe bin/zuraffa_mcp_server.dart -o zuraffa_mcp_server
# Run the server
./zuraffa_mcp_server
MCP Tools #
zuraffa_generate- Generate Clean Architecture codezuraffa_schema- Get JSON schema for config validationzuraffa_validate- Validate a generation config
For complete MCP documentation, see MCP_SERVER.md.
Project Structure #
Recommended folder structure for Clean Architecture:
lib/
βββ main.dart
βββ src/
βββ core/ # Shared utilities
β βββ error/ # Custom failures if needed
β βββ network/ # HTTP client, interceptors
β βββ utils/ # Helpers, extensions
β
βββ data/ # Data layer
β βββ datasources/ # Remote and local data sources
β βββ models/ # DTOs, JSON serialization
β βββ repositories/ # Repository implementations
β
βββ domain/ # Domain layer (pure Dart)
β βββ entities/ # Business objects
β βββ repositories/ # Repository interfaces
β βββ usecases/ # Business logic
β
βββ presentation/ # Presentation layer
βββ pages/ # Full-screen views
β βββ home/
β β βββ home_page.dart
β β βββ home_controller.dart
β β βββ home_state.dart
β βββ ...
βββ widgets/ # Reusable widgets
Advanced Features #
CancelToken #
Cooperative cancellation for long-running operations:
// Create a token
final cancelToken = CancelToken();
// Use with a use case
final result = await getUserUseCase(userId, cancelToken: cancelToken);
// Cancel when needed
cancelToken.cancel('User navigated away');
// Create with timeout
final timeoutToken = CancelToken.timeout(const Duration(seconds: 30));
// In Controllers, use createCancelToken() for automatic cleanup
class MyController extends Controller {
Future<void> loadData() async {
// Token automatically cancelled when controller disposes
final result = await execute(myUseCase, params);
}
}
ControlledWidgetSelector #
For fine-grained rebuilds when only specific values change:
// Only rebuilds when product.name changes
ControlledWidgetSelector<ProductController, String?>(
selector: (controller) => controller.viewState.product?.name,
builder: (context, productName) {
return Text(productName ?? 'Unknown');
},
)
Global Configuration #
void main() {
// Enable debug logging
Zuraffa.enableLogging();
runApp(MyApp());
}
// Access controllers from child widgets
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Zuraffa.getController<MyController>(context);
return ElevatedButton(
onPressed: () => controller.doSomething(),
child: Text('Action'),
);
}
}
Example #
See the example directory for a complete working application demonstrating:
- β UseCase for CRUD operations
- β StreamUseCase for real-time updates
- β BackgroundUseCase for CPU-intensive calculations
- β Controller with immutable state
- β CleanView with ControlledWidgetBuilder
- β CancelToken for cancellation
- β Error handling with AppFailure
Run the example:
cd example
flutter pub get
flutter run
Documentation #
- CLI Guide - Complete CLI documentation
- MCP Server - MCP server setup and usage
- AGENTS.md - Guide for AI coding agents
- Contributing - How to contribute
- Code of Conduct - Community guidelines
License #
MIT License - see LICENSE for details.
Authors #
- Ahmet TOK - GitHub
Made with β‘οΈ for the Flutter community