Okay, so I just started a new Android project and wanted to try implementing the Clean Architecture by Uncle Bob. I have a nice beginning using RxJava and stuff from GitHub samples & boilerplates and Fernando Cerjas' blog (like this article), but still have some questions on how to implement some UseCases.
TL;DR
Should an Entity have fields that are another Entity (in my example, User
having a List<Messages>
field)?
Or should the Presenter combine UseCases to build a ViewModel mapped on multiple Entities (then how to you code the mapper?)?
Or should the Presenter have a ViewModel associated to each UseCase/Entity, and create some kind of "wait for all data to onNext" to call the view.show() for each ViewModel?
Basically, should UseCases only return Entities? Can an Entity be composed of other entities (as in a field of the class)? Are Entities only dumb datamodels POJOs? How to you represent 'join SQL' queries?
As an example, let's take a simple users/messages app.
I want to implement two views: UserList
and UserDetails
:
UserList
displays a list ofUsers
UserDetails
displays a user's information and its latest messages.
UserList
is pretty straightforward, and I can see how to code the associated UseCase and layers (code below).
My problem is with the UserDetails
screen.
How should I code my GetUserInfoUseCase
if I want all the data to be passed at the view at the same time (like building a ViewModel composed of a User class, with a field List)? What should be the return value of the GetUserInfoUseCase
?
Should I code a Observable<User> GetUserInfoUseCase
and a Observable<List<Message>> GetUserLatestMessages
and merge them somehow in my presenter? If yes, how can I manage this, as I don't have the Observables in my Presenter (I'm passing only an Observer as my UseCases parameters)?
User Entity
public abstract class User {
public abstract long id();
public abstract String name();
...
}
Message Entity
public abstract class Message {
public abstract long id();
public abstract long senderId();
public abstract String text();
public abstract long timstamp();
...
}
GetUsersUseCase
public class GetUsersUseCase extends UseCaseObservableWithParameter<Boolean, List<User>, UsersRepository> {
@Inject
public GetUsersUseCase(UsersRepository UsersRepository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
super(usersRepository, threadScheduler, postExecutionScheduler);
}
@Override
protected Observable<List<User>> buildObservable(Boolean forceRefresh) {
if(forceRefresh)
repository.invalidateCache();
return repository.getUsers();
}
}
UsersPresenter
public class UsersPresenter extends BasePresenter<UsersContract.View> implements UsersContract.Presenter {
@Inject
GetUsersUseCase mGetUsersUseCase;
@Inject
UserViewModelMapper mUserMapper;
@Inject
public UsersPresenter() {
}
@Override
public void attachView(UsersContract.View mvpView) {
super.attachView(mvpView);
}
@Override
public void detachView() {
super.detachView();
mGetUsersUseCase.unsubscribe();
}
@Override
public void fetchUsers(boolean forceRefresh) {
getMvpView().showProgress();
mGetUsersUseCase.execute(forceRefresh, new DisposableObserver<List<User>>() {
@Override
public void onNext(List<User> users) {
getMvpView().hideProgress();
getMvpView().showUsers(mUsersMapper.mapUsersToViewModels(users));
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
getMvpView().hideProgress();
getMvpView().showErrorMessage(e.getMessage());
}
});
}
}
UseCaseObservableWithParameter
public abstract class UseCaseObservableWithParameter<REQUEST_DATA, RESPONSE_DATA, REPOSITORY> extends UseCase<Observable, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
public UseCaseObservableWithParameter(REPOSITORY repository, Scheduler threadScheduler, Scheduler postExecutionScheduler) {
super(repository, threadScheduler, postExecutionScheduler);
}
protected abstract Observable<RESPONSE_DATA> buildObservable(REQUEST_DATA requestData);
public void execute(REQUEST_DATA requestData, DisposableObserver<RESPONSE_DATA> useCaseSubscriber) {
this.disposable.add(
this.buildObservable(requestData)
.subscribeOn(threadScheduler)
.observeOn(postExecutionScheduler)
.subscribeWith(useCaseSubscriber)
);
}
}
UseCase
public abstract class UseCase<OBSERVABLE, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
protected final REPOSITORY repository;
protected final Scheduler threadScheduler;
protected final Scheduler postExecutionScheduler;
protected CompositeDisposable disposable = new CompositeDisposable();
public UseCase(REPOSITORY repository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
Timber.d("UseCase CTOR");
this.repository = repository;
this.threadScheduler = threadScheduler;
this.postExecutionScheduler = postExecutionScheduler;
}
protected abstract OBSERVABLE buildObservable(REQUEST_DATA requestData);
public boolean isUnsubscribed() {
return disposable.size() == 0;
}
public void unsubscribe() {
if (!isUnsubscribed()) {
disposable.clear();
}
}
}