From reactree-flutter-dev
Repository interface and implementation patterns with offline-first strategies
How this skill is triggered — by the user, by Claude, or both
Slash command
/reactree-flutter-dev:skills/repository-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```dart
// 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));
}
}
}
npx claudepluginhub Kaakati/rails-enterprise-dev --plugin reactree-flutter-devImplements repository pattern for Kotlin Multiplatform with shared interfaces in commonMain and platform-specific implementations for remote, local, and memory data sources.
Guides structuring Flutter apps as four-layer monorepos (Data, Repository, Business Logic, Presentation) with strict unidirectional dependencies and path-based local packages.
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.