From reactree-flutter-dev
Repository interface and implementation patterns with offline-first strategies
npx claudepluginhub Kaakati/rails-enterprise-dev --plugin reactree-flutter-devThis skill uses the workspace's default tool permissions.
```dart
Implements repository pattern for Kotlin Multiplatform with shared interfaces in commonMain and platform-specific implementations for remote, local, and memory data sources.
Implements Repository pattern with Service Layer for .NET data access abstraction using interfaces, async methods, and DI. Use for separating data logic from business logic or testable layers.
Structures Flutter monorepos with VGV layered architecture using Data, Repository, Business Logic, and Presentation layers with unidirectional dependencies. For multi-package apps and layer boundaries.
Share bugs, ideas, or general feedback.
// lib/domain/repositories/user_repository.dart
import 'package:dartz/dartz.dart';
import '../../core/errors/failures.dart';
import '../entities/user.dart';
abstract class UserRepository {
Future<Either<Failure, User>> getUser(String id);
Future<Either<Failure, List<User>>> getAllUsers();
Future<Either<Failure, User>> createUser(User user);
Future<Either<Failure, User>> updateUser(User user);
Future<Either<Failure, void>> deleteUser(String id);
}
// lib/data/repositories/user_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../core/errors/failures.dart';
import '../../core/errors/exceptions.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/user_repository.dart';
import '../providers/user_provider.dart';
class UserRepositoryImpl implements UserRepository {
final UserProvider _provider;
UserRepositoryImpl(this._provider);
@override
Future<Either<Failure, User>> getUser(String id) async {
try {
final model = await _provider.fetchUser(id);
return Right(model.toEntity());
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} on NetworkException {
return Left(NetworkFailure());
} catch (e) {
return Left(ServerFailure('Unexpected error: $e'));
}
}
}
class UserRepositoryImpl implements UserRepository {
final UserProvider _remoteSource;
final UserLocalSource _localSource;
final NetworkInfo _networkInfo;
UserRepositoryImpl(
this._remoteSource,
this._localSource,
this._networkInfo,
);
@override
Future<Either<Failure, User>> getUser(String id) async {
if (await _networkInfo.isConnected) {
// Try remote first
try {
final model = await _remoteSource.fetchUser(id);
// Cache for offline use
await _localSource.cacheUser(model);
return Right(model.toEntity());
} on ServerException catch (e) {
// Fallback to cache on server error
return _getCachedUser(id);
}
} else {
// Use cache when offline
return _getCachedUser(id);
}
}
Future<Either<Failure, User>> _getCachedUser(String id) async {
try {
final cached = await _localSource.getCachedUser(id);
if (cached != null) {
return Right(cached.toEntity());
} else {
return Left(CacheFailure('No cached data available'));
}
} on CacheException catch (e) {
return Left(CacheFailure(e.message));
}
}
@override
Future<Either<Failure, List<User>>> getAllUsers() async {
if (await _networkInfo.isConnected) {
try {
final models = await _remoteSource.fetchAllUsers();
await _localSource.cacheUsers(models);
return Right(models.map((m) => m.toEntity()).toList());
} on ServerException {
return _getCachedUsers();
}
} else {
return _getCachedUsers();
}
}
Future<Either<Failure, List<User>>> _getCachedUsers() async {
try {
final cached = await _localSource.getCachedUsers();
if (cached != null && cached.isNotEmpty) {
return Right(cached.map((m) => m.toEntity()).toList());
} else {
return Left(CacheFailure('No cached users'));
}
} on CacheException catch (e) {
return Left(CacheFailure(e.message));
}
}
}
// Try cache first, then network
Future<Either<Failure, User>> getUser(String id) async {
// Check cache first
final cached = await _localSource.getCachedUser(id);
if (cached != null) {
// Return cached data immediately
_refreshInBackground(id); // Update in background
return Right(cached.toEntity());
}
// Cache miss - fetch from network
return _fetchFromNetwork(id);
}
// Try network first, fallback to cache
Future<Either<Failure, User>> getUser(String id) async {
if (await _networkInfo.isConnected) {
try {
final model = await _remoteSource.fetchUser(id);
await _localSource.cacheUser(model);
return Right(model.toEntity());
} catch (e) {
return _getCachedUser(id); // Fallback
}
} else {
return _getCachedUser(id);
}
}
// Return cache immediately, then update with network data
Stream<Either<Failure, User>> getUserStream(String id) async* {
// Emit cached data first
final cached = await _localSource.getCachedUser(id);
if (cached != null) {
yield Right(cached.toEntity());
}
// Then fetch from network
if (await _networkInfo.isConnected) {
try {
final model = await _remoteSource.fetchUser(id);
await _localSource.cacheUser(model);
yield Right(model.toEntity());
} on ServerException catch (e) {
yield Left(ServerFailure(e.message));
}
}
}