Repository Generation Command
flx gen repository
কমান্ড Clean Architecture-এর Domain এবং Data layer-এ repository interface এবং implementation তৈরি করে।
Basic Usage
flx gen repository repository_name
Advanced Usage
flx gen repository repository_name --feature=user --data-source=api --with-cache --with-offline
Available Options
Option | Description | Values | Default |
---|---|---|---|
--feature | Target feature name | Any valid feature | Required |
--data-source | Primary data source | api , local , both | api |
--with-cache | Include caching logic | true , false | false |
--with-offline | Include offline support | true , false | false |
--crud-operations | Include CRUD methods | true , false | true |
Generated Structure
lib/features/{feature}/
├── domain/
│ └── repositories/
│ └── {name}_repository.dart
└── data/
├── repositories/
│ └── {name}_repository_impl.dart
└── datasources/
├── {name}_local_data_source.dart
└── {name}_remote_data_source.dart
Examples
1. Basic User Repository
flx gen repository user --feature=user
Generated Domain Repository Interface:
// lib/features/user/domain/repositories/user_repository.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/user_entity.dart';
abstract class UserRepository {
Future<Either<Failure, UserEntity>> getUser(String id);
Future<Either<Failure, List<UserEntity>>> getUsers({
int page = 1,
int limit = 10,
String? searchQuery,
});
Future<Either<Failure, UserEntity>> createUser(UserEntity user);
Future<Either<Failure, UserEntity>> updateUser(UserEntity user);
Future<Either<Failure, void>> deleteUser(String id);
// Optional methods for specific use cases
Future<Either<Failure, UserEntity>> getUserByEmail(String email);
Future<Either<Failure, List<UserEntity>>> getUsersByRole(String role);
Stream<Either<Failure, UserEntity>> watchUser(String id);
}
Generated Repository Implementation:
// lib/features/user/data/repositories/user_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/network/network_info.dart';
import '../../domain/entities/user_entity.dart';
import '../../domain/repositories/user_repository.dart';
import '../datasources/user_local_data_source.dart';
import '../datasources/user_remote_data_source.dart';
import '../models/user_model.dart';
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource remoteDataSource;
final UserLocalDataSource localDataSource;
final NetworkInfo networkInfo;
UserRepositoryImpl({
required this.remoteDataSource,
required this.localDataSource,
required this.networkInfo,
});
Future<Either<Failure, UserEntity>> getUser(String id) async {
if (await networkInfo.isConnected) {
try {
final remoteUser = await remoteDataSource.getUser(id);
localDataSource.cacheUser(remoteUser);
return Right(remoteUser.toEntity());
} on ServerException {
return Left(ServerFailure());
} on NetworkException {
return Left(NetworkFailure());
}
} else {
try {
final localUser = await localDataSource.getLastUser(id);
return Right(localUser.toEntity());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, List<UserEntity>>> getUsers({
int page = 1,
int limit = 10,
String? searchQuery,
}) async {
if (await networkInfo.isConnected) {
try {
final remoteUsers = await remoteDataSource.getUsers(
page: page,
limit: limit,
searchQuery: searchQuery,
);
// Cache the users
localDataSource.cacheUsers(remoteUsers);
return Right(remoteUsers.map((model) => model.toEntity()).toList());
} on ServerException {
return Left(ServerFailure());
} on NetworkException {
return Left(NetworkFailure());
}
} else {
try {
final localUsers = await localDataSource.getCachedUsers();
return Right(localUsers.map((model) => model.toEntity()).toList());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, UserEntity>> createUser(UserEntity user) async {
if (await networkInfo.isConnected) {
try {
final userModel = UserModel.fromEntity(user);
final createdUser = await remoteDataSource.createUser(userModel);
// Cache the new user
localDataSource.cacheUser(createdUser);
return Right(createdUser.toEntity());
} on ServerException {
return Left(ServerFailure());
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
}
} else {
return Left(NetworkFailure());
}
}
Future<Either<Failure, UserEntity>> updateUser(UserEntity user) async {
if (await networkInfo.isConnected) {
try {
final userModel = UserModel.fromEntity(user);
final updatedUser = await remoteDataSource.updateUser(userModel);
// Update cache
localDataSource.cacheUser(updatedUser);
return Right(updatedUser.toEntity());
} on ServerException {
return Left(ServerFailure());
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
}
} else {
return Left(NetworkFailure());
}
}
Future<Either<Failure, void>> deleteUser(String id) async {
if (await networkInfo.isConnected) {
try {
await remoteDataSource.deleteUser(id);
// Remove from cache
localDataSource.removeUser(id);
return const Right(null);
} on ServerException {
return Left(ServerFailure());
}
} else {
return Left(NetworkFailure());
}
}
Future<Either<Failure, UserEntity>> getUserByEmail(String email) async {
if (await networkInfo.isConnected) {
try {
final remoteUser = await remoteDataSource.getUserByEmail(email);
localDataSource.cacheUser(remoteUser);
return Right(remoteUser.toEntity());
} on ServerException {
return Left(ServerFailure());
} on NotFoundException {
return Left(NotFoundFailure());
}
} else {
try {
final localUser = await localDataSource.getUserByEmail(email);
return Right(localUser.toEntity());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, List<UserEntity>>> getUsersByRole(String role) async {
if (await networkInfo.isConnected) {
try {
final remoteUsers = await remoteDataSource.getUsersByRole(role);
return Right(remoteUsers.map((model) => model.toEntity()).toList());
} on ServerException {
return Left(ServerFailure());
}
} else {
try {
final localUsers = await localDataSource.getUsersByRole(role);
return Right(localUsers.map((model) => model.toEntity()).toList());
} on CacheException {
return Left(CacheFailure());
}
}
}
Stream<Either<Failure, UserEntity>> watchUser(String id) {
return remoteDataSource.watchUser(id).map((userModel) {
try {
// Cache the updated user
localDataSource.cacheUser(userModel);
return Right(userModel.toEntity());
} catch (e) {
return Left(UnexpectedFailure(e.toString()));
}
}).handleError((error) {
if (error is ServerException) {
return Left(ServerFailure());
}
return Left(UnexpectedFailure(error.toString()));
});
}
}
2. Repository with Advanced Caching
flx gen repository product --feature=product --with-cache --with-offline
Generated Advanced Repository Implementation:
// lib/features/product/data/repositories/product_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../../../core/cache/cache_manager.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/network/network_info.dart';
import '../../domain/entities/product_entity.dart';
import '../../domain/repositories/product_repository.dart';
import '../datasources/product_local_data_source.dart';
import '../datasources/product_remote_data_source.dart';
import '../models/product_model.dart';
class ProductRepositoryImpl implements ProductRepository {
final ProductRemoteDataSource remoteDataSource;
final ProductLocalDataSource localDataSource;
final NetworkInfo networkInfo;
final CacheManager cacheManager;
// Cache configuration
static const String _productCacheKey = 'products';
static const Duration _cacheExpiry = Duration(hours: 1);
ProductRepositoryImpl({
required this.remoteDataSource,
required this.localDataSource,
required this.networkInfo,
required this.cacheManager,
});
Future<Either<Failure, List<ProductEntity>>> getProducts({
int page = 1,
int limit = 20,
String? category,
String? searchQuery,
ProductSortBy? sortBy,
}) async {
final cacheKey = '$_productCacheKey-$page-$limit-$category-$searchQuery-$sortBy';
// Check cache first
if (cacheManager.hasValidCache(cacheKey, _cacheExpiry)) {
try {
final cachedProducts = await localDataSource.getCachedProductsWithParams(
page: page,
limit: limit,
category: category,
searchQuery: searchQuery,
sortBy: sortBy,
);
return Right(cachedProducts.map((model) => model.toEntity()).toList());
} on CacheException {
// Cache miss, proceed to network
}
}
if (await networkInfo.isConnected) {
try {
final remoteProducts = await remoteDataSource.getProducts(
page: page,
limit: limit,
category: category,
searchQuery: searchQuery,
sortBy: sortBy,
);
// Update cache with fresh data
await _updateProductCache(remoteProducts, cacheKey);
return Right(remoteProducts.map((model) => model.toEntity()).toList());
} on ServerException {
return _handleServerError(cacheKey);
} on NetworkException {
return _handleNetworkError(cacheKey);
}
} else {
// Offline mode
return _getOfflineProducts(page: page, limit: limit, category: category);
}
}
Future<Either<Failure, ProductEntity>> getProduct(String id) async {
final cacheKey = 'product-$id';
// Try cache first for better performance
if (cacheManager.hasValidCache(cacheKey, _cacheExpiry)) {
try {
final cachedProduct = await localDataSource.getLastProduct(id);
return Right(cachedProduct.toEntity());
} on CacheException {
// Proceed to network
}
}
if (await networkInfo.isConnected) {
try {
final remoteProduct = await remoteDataSource.getProduct(id);
// Cache individual product
await localDataSource.cacheProduct(remoteProduct);
cacheManager.setCacheTimestamp(cacheKey);
return Right(remoteProduct.toEntity());
} on ServerException {
return _handleServerError(cacheKey);
} on NotFoundException {
return Left(NotFoundFailure());
}
} else {
// Try to get from offline storage
try {
final offlineProduct = await localDataSource.getOfflineProduct(id);
return Right(offlineProduct.toEntity());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, ProductEntity>> createProduct(ProductEntity product) async {
if (await networkInfo.isConnected) {
try {
final productModel = ProductModel.fromEntity(product);
final createdProduct = await remoteDataSource.createProduct(productModel);
// Update cache
await localDataSource.cacheProduct(createdProduct);
_invalidateProductListCache();
return Right(createdProduct.toEntity());
} on ServerException {
return Left(ServerFailure());
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
}
} else {
// Queue for sync when online
try {
final productModel = ProductModel.fromEntity(product);
await localDataSource.queueProductForSync(productModel, SyncAction.create);
// Add to offline storage with pending status
final offlineProduct = productModel.copyWith(
syncStatus: SyncStatus.pending,
lastModified: DateTime.now(),
);
await localDataSource.cacheProduct(offlineProduct);
return Right(offlineProduct.toEntity());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, ProductEntity>> updateProduct(ProductEntity product) async {
if (await networkInfo.isConnected) {
try {
final productModel = ProductModel.fromEntity(product);
final updatedProduct = await remoteDataSource.updateProduct(productModel);
// Update cache
await localDataSource.cacheProduct(updatedProduct);
_invalidateProductListCache();
return Right(updatedProduct.toEntity());
} on ServerException {
return Left(ServerFailure());
} on ValidationException catch (e) {
return Left(ValidationFailure(e.message));
}
} else {
// Queue for sync when online
try {
final productModel = ProductModel.fromEntity(product);
await localDataSource.queueProductForSync(productModel, SyncAction.update);
// Update offline storage
final offlineProduct = productModel.copyWith(
syncStatus: SyncStatus.pending,
lastModified: DateTime.now(),
);
await localDataSource.cacheProduct(offlineProduct);
return Right(offlineProduct.toEntity());
} on CacheException {
return Left(CacheFailure());
}
}
}
Future<Either<Failure, void>> deleteProduct(String id) async {
if (await networkInfo.isConnected) {
try {
await remoteDataSource.deleteProduct(id);
// Remove from cache
await localDataSource.removeProduct(id);
_invalidateProductListCache();
return const Right(null);
} on ServerException {
return Left(ServerFailure());
}
} else {
// Queue for sync when online
try {
await localDataSource.queueProductForSync(
ProductModel(id: id, name: '', description: '', price: 0.0, category: ''),
SyncAction.delete,
);
// Mark as deleted in offline storage
await localDataSource.markProductAsDeleted(id);
return const Right(null);
} on CacheException {
return Left(CacheFailure());
}
}
}
// Sync methods for offline support
Future<Either<Failure, void>> syncPendingChanges() async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure());
}
try {
final pendingActions = await localDataSource.getPendingSyncActions();
for (final action in pendingActions) {
switch (action.type) {
case SyncAction.create:
await remoteDataSource.createProduct(action.product);
break;
case SyncAction.update:
await remoteDataSource.updateProduct(action.product);
break;
case SyncAction.delete:
await remoteDataSource.deleteProduct(action.product.id);
break;
}
// Mark as synced
await localDataSource.markAsSynced(action.id);
}
// Clear cache to force fresh data
_invalidateProductListCache();
return const Right(null);
} on ServerException {
return Left(ServerFailure());
} on CacheException {
return Left(CacheFailure());
}
}
// Helper methods
Future<void> _updateProductCache(List<ProductModel> products, String cacheKey) async {
await localDataSource.cacheProducts(products);
cacheManager.setCacheTimestamp(cacheKey);
}
Either<Failure, List<ProductEntity>> _handleServerError(String cacheKey) {
// Try to return stale cache on server error
try {
final staleProducts = localDataSource.getStaleProducts();
return Right(staleProducts.map((model) => model.toEntity()).toList());
} on CacheException {
return Left(ServerFailure());
}
}
Either<Failure, List<ProductEntity>> _handleNetworkError(String cacheKey) {
// Return cached data on network error
try {
final cachedProducts = localDataSource.getAllCachedProducts();
return Right(cachedProducts.map((model) => model.toEntity()).toList());
} on CacheException {
return Left(NetworkFailure());
}
}
Future<Either<Failure, List<ProductEntity>>> _getOfflineProducts({
int page = 1,
int limit = 20,
String? category,
}) async {
try {
final offlineProducts = await localDataSource.getOfflineProducts(
page: page,
limit: limit,
category: category,
);
return Right(offlineProducts.map((model) => model.toEntity()).toList());
} on CacheException {
return Left(CacheFailure());
}
}
void _invalidateProductListCache() {
cacheManager.invalidateCache(_productCacheKey);
}
}
enum SyncAction { create, update, delete }
enum SyncStatus { pending, synced, failed }
Best Practices
- Interface Segregation: Repository interface-এ শুধু প্রয়োজনীয় methods রাখুন
- Error Handling: Comprehensive error handling implement করুন
- Caching Strategy: Smart caching এবং cache invalidation ব্যবহার করুন
- Offline Support: Critical operations-এর জন্য offline support যোগ করুন
- Testing: Mock data sources ব্যবহার করে unit tests লিখুন
Dependencies
Repository generation-এর জন্য এই packages প্রয়োজন:
dependencies:
dartz: ^0.10.1
connectivity_plus: ^4.0.2
hive: ^2.2.3
http: ^1.1.0
dev_dependencies:
mockito: ^5.4.2
test: ^1.24.6
এই command ব্যবহার করে আপনি robust এবং scalable data layer তৈরি করতে পারবেন!