Provide me the clean architechture stucture along with feature first folder structure along with models , entities , api calls, remote - for offline first , riverpod statemanagement ?
#state#riverpod#api#clean
Answer
Overview
This is a complete clean architecture + feature-first folder structure with Riverpod, including offline-first data layer, API calls, entities, models, and remote/local data sources.
Folder Structure
textlib/ ├── core/ │ ├── constants/ │ │ └── api_constants.dart │ ├── errors/ │ │ └── failures.dart │ ├── network/ │ │ ├── api_client.dart ← Dio setup │ │ └── network_info.dart ← Check connectivity │ └── utils/ │ └── result.dart ← Either<Failure, T> │ ├── features/ │ └── products/ │ ├── data/ │ │ ├── datasources/ │ │ │ ├── product_remote_datasource.dart ← API calls │ │ │ └── product_local_datasource.dart ← SQLite/Hive cache │ │ ├── models/ │ │ │ └── product_model.dart ← JSON serializable │ │ └── repositories/ │ │ └── product_repository_impl.dart ← offline-first logic │ ├── domain/ │ │ ├── entities/ │ │ │ └── product_entity.dart ← Pure Dart class │ │ ├── repositories/ │ │ │ └── product_repository.dart ← Abstract interface │ │ └── usecases/ │ │ ├── get_products_usecase.dart │ │ └── get_product_detail_usecase.dart │ └── presentation/ │ ├── providers/ │ │ └── products_provider.dart ← Riverpod providers │ ├── screens/ │ │ └── products_screen.dart │ └── widgets/ │ └── product_card.dart │ ├── providers.dart ← Barrel file └── main.dart
Domain Layer — Pure Dart, No Dependencies
dart// features/products/domain/entities/product_entity.dart class ProductEntity { final int id; final String name; final double price; final String imageUrl; ProductEntity({required this.id, required this.name, required this.price, required this.imageUrl}); } // features/products/domain/repositories/product_repository.dart abstract class ProductRepository { Future<Either<Failure, List<ProductEntity>>> getProducts(); Future<Either<Failure, ProductEntity>> getProductById(int id); } // features/products/domain/usecases/get_products_usecase.dart class GetProductsUseCase { final ProductRepository repository; GetProductsUseCase(this.repository); Future<Either<Failure, List<ProductEntity>>> call() { return repository.getProducts(); } }
Data Layer — Models + DataSources
dart// features/products/data/models/product_model.dart class ProductModel extends ProductEntity { ProductModel({required super.id, required super.name, required super.price, required super.imageUrl}); factory ProductModel.fromJson(Map<String, dynamic> json) => ProductModel( id: json['id'], name: json['name'], price: json['price'].toDouble(), imageUrl: json['imageUrl'], ); Map<String, dynamic> toJson() => {'id': id, 'name': name, 'price': price, 'imageUrl': imageUrl}; } // Remote datasource — API calls abstract class ProductRemoteDataSource { Future<List<ProductModel>> getProducts(); } class ProductRemoteDataSourceImpl implements ProductRemoteDataSource { final Dio dio; ProductRemoteDataSourceImpl(this.dio); Future<List<ProductModel>> getProducts() async { final res = await dio.get('/products'); return (res.data as List).map((e) => ProductModel.fromJson(e)).toList(); } } // Local datasource — offline cache abstract class ProductLocalDataSource { Future<List<ProductModel>> getCachedProducts(); Future<void> cacheProducts(List<ProductModel> products); }
Repository — Offline-First
dartclass ProductRepositoryImpl implements ProductRepository { final ProductRemoteDataSource remoteDataSource; final ProductLocalDataSource localDataSource; final NetworkInfo networkInfo; ProductRepositoryImpl({ required this.remoteDataSource, required this.localDataSource, required this.networkInfo, }); Future<Either<Failure, List<ProductEntity>>> getProducts() async { if (await networkInfo.isConnected) { try { final products = await remoteDataSource.getProducts(); await localDataSource.cacheProducts(products); // Cache for offline return Right(products); } catch (e) { return Left(ServerFailure(e.toString())); } } else { // Offline — return cached data try { final cached = await localDataSource.getCachedProducts(); return Right(cached); } catch (e) { return Left(CacheFailure('No cached data available')); } } } }
Riverpod Providers
dart// features/products/presentation/providers/products_provider.dart final dioProvider = Provider((ref) => Dio(BaseOptions(baseUrl: ApiConstants.baseUrl))); final productRemoteDataSourceProvider = Provider( (ref) => ProductRemoteDataSourceImpl(ref.watch(dioProvider)), ); final productLocalDataSourceProvider = Provider( (ref) => ProductLocalDataSourceImpl(), // Hive/SQLite ); final productRepositoryProvider = Provider( (ref) => ProductRepositoryImpl( remoteDataSource: ref.watch(productRemoteDataSourceProvider), localDataSource: ref.watch(productLocalDataSourceProvider), networkInfo: ref.watch(networkInfoProvider), ), ); final productsProvider = AsyncNotifierProvider<ProductsNotifier, List<ProductEntity>>( ProductsNotifier.new, ); class ProductsNotifier extends AsyncNotifier<List<ProductEntity>> { Future<List<ProductEntity>> build() async { final result = await ref.watch(productRepositoryProvider).getProducts(); return result.fold( (failure) => throw failure, (products) => products, ); } }
Screen — Consume Provider
dartclass ProductsScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(productsProvider); return state.when( loading: () => CircularProgressIndicator(), error: (e, _) => Text('Error: $e'), data: (products) => ListView.builder( itemCount: products.length, itemBuilder: (_, i) => ProductCard(product: products[i]), ), ); } }
Key Principle: Domain layer has zero dependencies on Flutter or external packages. Data layer implements domain interfaces. Presentation only calls use cases through providers — never touches data layer directly.