Overview
The Apiary repository provides the data access layer for managing apiaries in the Softbee application. Following Clean Architecture principles, it defines an abstract interface in the domain layer and a concrete implementation in the data layer.
The repository handles:
Authentication token management
Communication with remote data sources
Error handling and failure mapping
Data transformation between layers
Repository Interface
Source: lib/feature/apiaries/domain/repositories/apiary_repository.dart
Definition
abstract class ApiaryRepository {
Future < Either < Failure , List < Apiary >>> getApiaries ();
Future < Either < Failure , Apiary >> createApiary (
String userId,
String name,
String ? location,
int ? beehivesCount,
bool treatments,
);
Future < Either < Failure , Apiary >> updateApiary (
String apiaryId,
String userId,
String ? name,
String ? location,
int ? beehivesCount,
bool ? treatments,
);
Future < Either < Failure , void >> deleteApiary (
String apiaryId,
String userId,
);
}
Methods
getApiaries
Retrieves all apiaries for the authenticated user.
Future < Either < Failure , List < Apiary >>> getApiaries ()
A list of all apiaries belonging to the authenticated user.
AuthFailure: No authentication token found
ServerFailure: Network or server error
createApiary
Creates a new apiary.
Future < Either < Failure , Apiary >> createApiary (
String userId,
String name,
String ? location,
int ? beehivesCount,
bool treatments,
)
The ID of the user creating the apiary.
The name of the new apiary.
Optional location of the apiary.
Optional initial beehive count.
Whether treatments are being applied.
The newly created apiary.
AuthFailure: No authentication token found
ServerFailure: Network or server error
updateApiary
Updates an existing apiary.
Future < Either < Failure , Apiary >> updateApiary (
String apiaryId,
String userId,
String ? name,
String ? location,
int ? beehivesCount,
bool ? treatments,
)
The ID of the apiary to update.
The ID of the user performing the update (for authorization).
New name for the apiary (null = no change).
New location (null = no change).
New beehive count (null = no change).
New treatments status (null = no change).
AuthFailure: No authentication token found or unauthorized
ServerFailure: Network or server error
NotFoundFailure: Apiary not found
deleteApiary
Deletes an apiary.
Future < Either < Failure , void >> deleteApiary (
String apiaryId,
String userId,
)
The ID of the apiary to delete.
The ID of the user performing the deletion (for authorization).
Void on successful deletion.
AuthFailure: No authentication token found or unauthorized
ServerFailure: Network or server error
NotFoundFailure: Apiary not found
Repository Implementation
Source: lib/feature/apiaries/data/repositories/apiary_repository_impl.dart
Class Definition
class ApiaryRepositoryImpl implements ApiaryRepository {
final ApiaryRemoteDataSource remoteDataSource;
final AuthLocalDataSource localDataSource;
ApiaryRepositoryImpl ({
required this .remoteDataSource,
required this .localDataSource,
});
}
Dependencies
remoteDataSource
ApiaryRemoteDataSource
required
Handles HTTP requests to the backend API for apiary operations.
localDataSource
AuthLocalDataSource
required
Provides access to locally stored authentication tokens.
Implementation Details
The implementation follows a consistent pattern for all methods:
Retrieve authentication token from local storage
Validate token presence
Call remote data source with token and parameters
Handle exceptions and map to appropriate failure types
Return Either<Failure, T> result
getApiaries Implementation
@override
Future < Either < Failure , List < Apiary >>> getApiaries () async {
try {
final token = await localDataSource. getToken ();
if (token == null ) {
return const Left ( AuthFailure ( 'No authentication token found.' ));
}
final result = await remoteDataSource. getApiaries (token);
return Right (result);
} catch (e) {
return Left ( ServerFailure (e. toString ()));
}
}
Check for authentication token
If missing, return AuthFailure
Call remote data source
Catch any exceptions and wrap in ServerFailure
Return success result or failure
createApiary Implementation
@override
Future < Either < Failure , Apiary >> createApiary (
String userId,
String name,
String ? location,
int ? beehivesCount,
bool treatments,
) async {
try {
final token = await localDataSource. getToken ();
if (token == null ) {
return const Left ( AuthFailure ( 'No authentication token found.' ));
}
final result = await remoteDataSource. createApiary (
token,
userId,
name,
location,
beehivesCount,
treatments,
);
return Right (result);
} catch (e) {
return Left ( ServerFailure (e. toString ()));
}
}
updateApiary Implementation
@override
Future < Either < Failure , Apiary >> updateApiary (
String apiaryId,
String userId,
String ? name,
String ? location,
int ? beehivesCount,
bool ? treatments,
) async {
try {
final token = await localDataSource. getToken ();
if (token == null ) {
return const Left ( AuthFailure ( 'No authentication token found.' ));
}
final result = await remoteDataSource. updateApiary (
token,
apiaryId,
userId,
name,
location,
beehivesCount,
treatments,
);
return Right (result);
} catch (e) {
return Left ( ServerFailure (e. toString ()));
}
}
deleteApiary Implementation
@override
Future < Either < Failure , void >> deleteApiary (
String apiaryId,
String userId,
) async {
try {
final token = await localDataSource. getToken ();
if (token == null ) {
return const Left ( AuthFailure ( 'No authentication token found.' ));
}
await remoteDataSource. deleteApiary (token, apiaryId, userId);
return const Right ( null );
} catch (e) {
return Left ( ServerFailure (e. toString ()));
}
}
Usage Example
Setting Up the Repository
import 'package:get_it/get_it.dart' ;
final getIt = GetIt .instance;
void setupRepositories () {
// Register data sources
getIt. registerLazySingleton < ApiaryRemoteDataSource >(
() => ApiaryRemoteDataSourceImpl (httpClient : getIt ()),
);
getIt. registerLazySingleton < AuthLocalDataSource >(
() => AuthLocalDataSourceImpl (secureStorage : getIt ()),
);
// Register repository
getIt. registerLazySingleton < ApiaryRepository >(
() => ApiaryRepositoryImpl (
remoteDataSource : getIt (),
localDataSource : getIt (),
),
);
}
Using the Repository
class ApiaryService {
final ApiaryRepository repository;
ApiaryService ( this .repository);
Future < void > loadApiaries () async {
final result = await repository. getApiaries ();
result. fold (
(failure) {
if (failure is AuthFailure ) {
print ( 'Please log in' );
} else {
print ( 'Error: ${ failure . message } ' );
}
},
(apiaries) {
print ( 'Loaded ${ apiaries . length } apiaries' );
},
);
}
Future < void > addApiary ( String name, String location) async {
final result = await repository. createApiary (
'user_123' ,
name,
location,
null ,
false ,
);
result. fold (
(failure) => print ( 'Failed to create: ${ failure . message } ' ),
(apiary) => print ( 'Created: ${ apiary . name } ' ),
);
}
}
Testing
Mocking the Repository
import 'package:mockito/mockito.dart' ;
import 'package:mockito/annotations.dart' ;
@GenerateMocks ([ ApiaryRepository ])
import 'apiary_repository_test.mocks.dart' ;
void main () {
late MockApiaryRepository mockRepository;
setUp (() {
mockRepository = MockApiaryRepository ();
});
test ( 'should return list of apiaries on success' , () async {
// Arrange
final apiaries = [
Apiary (id : '1' , userId : 'user1' , name : 'Test' , treatments : false ),
];
when (mockRepository. getApiaries ())
. thenAnswer ((_) async => Right (apiaries));
// Act
final result = await mockRepository. getApiaries ();
// Assert
expect (result.isRight, true );
result. fold (
(failure) => fail ( 'Should not fail' ),
(data) => expect (data.length, 1 ),
);
});
test ( 'should return AuthFailure when token missing' , () async {
// Arrange
when (mockRepository. getApiaries ())
. thenAnswer ((_) async => const Left ( AuthFailure ( 'No token' )));
// Act
final result = await mockRepository. getApiaries ();
// Assert
expect (result.isLeft, true );
result. fold (
(failure) => expect (failure, isA < AuthFailure >()),
(data) => fail ( 'Should not succeed' ),
);
});
}
Architecture Benefits
Show Separation of Concerns
The repository pattern separates business logic (domain layer) from data access details (data layer), making the code more maintainable and testable.
The abstract interface allows for easy mocking in tests, enabling unit testing of use cases without requiring actual network calls.
The implementation can be swapped without affecting the domain layer. For example, you could add caching, offline support, or switch to a different backend.
Centralized error handling ensures consistent failure types across the application, making it easier to handle errors at the presentation layer.