Reputation: 133
I am currently implementing Clean Architecture in a Flutter app, dividing it into three layers: domain, data, and presentation. The domain layer consists of use cases, entities, and repository interfaces, the data layer has repository implementations and API interfaces & implementations (data sources), while the presentation layer contains the UI and Bloc.
The dependency flow is as follows: Blocs depend on use cases; use cases depend on repositories, and repositories rely on data sources.
I am facing a specific scenario where I want to ensure independent pages can listen to changes made in different parts of the app. One common situation is having a page displaying a list of T items, from which users can navigate to an item registration page in a deeper route. After returning to the list page, I need to refresh the list dynamically.
Another use case involves authentication, where I use a UserRepository to store the authenticated user. I want my app's UI to update dynamically when the user's authentication status changes. For instance, if an authentication function succeeds and returns a user, I update the current user variable and expect the index page to listen to this change and log the user in instantly. To achieve this, I made the UserRepository class a ChangeNotifier, and I notify listeners whenever the current user changes.
However, I am concerned that this approach might be in conflict with the principles of Clean Architecture. Specifically, I am required to declare a getter for the repository in the use case for the bloc to subscribe to changes.
I want to ensure my implementation respects Clean Architecture principles and is not just a workaround. Am I correctly applying Clean Architecture or am I inadvertently breaking it? Any suggestions or alternatives on handling these scenarios within the Clean Architecture structure would be greatly appreciated. Thank you!
Here is a snipped for one of the examples stated above.
class UserRepository extends ChangeNotifier implements IUserRepository {
const UserRepository({
required IUserApi userApi,
}) : _userApi = userApi;
final IUserApi _userApi;
User? currentUser;
Future<User> logIn(String token) {
try{
final user = await _userApi.logIn(token);
currentUser = user;
notifyListeners();
return user;
} catch(e){
currentUser = null;
notifyListeners();
}
}
}
class GetUserUseCases {
GetUserUseCases({
required IUserRepository userRepository,
}) : _user = userRepository;
final IUserRepository _user;
IUserRepository get userRepository => _user;
Future<User> execute(String token) => _user.logIn(token);
}
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc({
required GetUserUseCases getUserUseCases,
}) : _getUserUseCases = getUserUseCases,
{
on<AuthEventLoggedIn>(_onLogIn);
on<AuthEventLoggedOut>(_onLogOut);
// Other event subscriptions ...
// Listen to the user repository changes and update the state
// in real time.
_getUserUseCases.userRepository.listen((repository) {
if (repository.currentUser != null) {
add(AuthEventLoggedIn(user));
} else {
add(const AuthEventLoggedOut());
}
});
}
final GetUserUseCases _getUserUseCases;
// Event methods ...
}
Upvotes: 0
Views: 794
Reputation: 1231
The first thing that I noticed is your UserRepository
extends ChangeNotifier
which contains an internal state of type User?
currentUser. A state should be stored and handled by a Bloc
, in your case, AuthBloc
. Your AuthBloc
should be holding an internal state AuthState
that can tell other parts of the app what the currentUser
is, and it should be responsible to mutate its state, i.e. handle the login and log out event.
Secondly, I would say UserRepository
should not need to implement IUserRepository
most of the time, unless there are 2 different implementations of UserRepository
. It makes code navigating in the IDE much harder and does not bring too much value. If you want to mock UserRepository
in tests, you can already do that by extending UserRepository
.
Thirdly, UseCase
's act as an abstraction layer between the intention and the actual implementation. You should not need to define a UseCase
class since Bloc
already did that for you, you can use the AuthEvent
instead.
Upvotes: 0