Refactored Interactos and use cases. Added ThreadExecutor. Some modifications to UserListPresenter

This commit is contained in:
Fernando Cejas
2014-08-29 01:10:03 +02:00
parent 00b5b92edc
commit 40618ba850
13 changed files with 217 additions and 75 deletions

View File

@ -0,0 +1,56 @@
/**
* Copyright (C) 2014 android10.org. All rights reserved.
* @author Fernando Cejas (the android10 coder)
*/
package com.fernandocejas.android10.sample.data.executor;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Decorated {@link java.util.concurrent.ThreadPoolExecutor} Singleton class based on
* 'Initialization on Demand Holder' pattern.
*/
public class JobExecutor implements ThreadExecutor {
private static class LazyHolder {
private static final JobExecutor INSTANCE = new JobExecutor();
}
public static JobExecutor getInstance() {
return LazyHolder.INSTANCE;
}
private static final int INITIAL_POOL_SIZE = 3;
private static final int MAX_POOL_SIZE = 5;
// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 10;
// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
private final BlockingQueue<Runnable> workQueue;
private final ThreadPoolExecutor threadPoolExecutor;
private JobExecutor() {
this.workQueue = new LinkedBlockingQueue<Runnable>();
this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE,
KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue);
}
/**
* {@inheritDoc}
* @param runnable The class that implements {@link Runnable} interface.
*/
@Override public void execute(Runnable runnable) {
if (runnable == null) {
throw new IllegalArgumentException("Runnable to execute cannot be null");
}
this.threadPoolExecutor.execute(runnable);
}
}

View File

@ -5,12 +5,15 @@
package com.fernandocejas.android10.sample.domain.executor;
/**
* UI thread abstraction created to change the execution context from any thread to the UI thread.
* Thread abstraction created to change the execution context from any thread to any other thread.
* Useful to encapsulate a UI Thread for example, since some job will be done in background, an
* implementation of this interface will change context and update the UI.
*/
public interface MainThread {
public interface PostExecutionThread {
/**
* Causes the {@link Runnable} to be added to the message queue of the Main UI Thread
* of the application.
*
* @param runnable {@link Runnable} to be executed.
*/
void post(Runnable runnable);

View File

@ -12,13 +12,7 @@ import com.fernandocejas.android10.sample.domain.interactor.Interactor;
*
* Use this class to execute an {@link Interactor}.
*/
public interface Executor {
/**
* Executes an {@link Interactor} by creating a new runnable and execute its run() method.
* @param interactor The {@link Interactor} to execute.
*/
void execute(final Interactor interactor);
public interface ThreadExecutor {
/**
* Executes a {@link Runnable}.
* @param runnable The class that implements {@link Runnable} interface.

View File

@ -9,13 +9,24 @@ import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
import java.util.Collection;
/**
*
* This interface represents a execution unit for a use case to get a collection of {@link User}.
* By convention this use case (Interactor) implementation will return the result using a Callback.
* That callback should be executed in the UI thread.
*/
public interface GetUserListUseCase extends Interactor {
/**
* Callback used to be notified when either a users collection has been loaded or an error
* happened.
*/
interface Callback {
void onUserListLoaded(Collection<User> usersCollection);
void onError(ErrorBundle errorBundle);
}
void getUserList(Callback callback);
/**
* Executes this user case.
*
* @param callback A {@link GetUserListUseCase.Callback} used to notify the client.
*/
void execute(Callback callback);
}

View File

@ -4,35 +4,79 @@
*/
package com.fernandocejas.android10.sample.domain.interactor;
import com.fernandocejas.android10.sample.domain.User;
import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import java.util.Collection;
import java.util.Collections;
/**
*
* This class is an implementation of {@link GetUserListUseCase} that represents a use case for
* retrieving a collection of all {@link User}.
*/
public class GetUserListUseCaseImpl implements GetUserListUseCase {
private final UserRepository userRepository;
private final ThreadExecutor threadExecutor;
private final PostExecutionThread postExecutionThread;
public GetUserListUseCaseImpl(UserRepository userRepository) {
if (userRepository == null) {
private Callback callback;
/**
* Constructor of the class.
*
* @param userRepository A {@link UserRepository} as a source for retrieving data.
* @param threadExecutor {@link ThreadExecutor} used to execute this use case in a background thread.
* @param postExecutionThread {@link PostExecutionThread} used to post updates when the use case has been executed.
*/
public GetUserListUseCaseImpl(UserRepository userRepository, ThreadExecutor threadExecutor,
PostExecutionThread postExecutionThread) {
if (userRepository == null || threadExecutor == null || postExecutionThread == null) {
throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
}
this.userRepository = userRepository;
this.threadExecutor = threadExecutor;
this.postExecutionThread = postExecutionThread;
}
@Override public void execute(Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("Interactor callback cannot be null!!!");
}
this.callback = callback;
this.threadExecutor.execute(this);
}
@Override public void run() {
this.userRepository.getUserList(this.repositoryCallback);
}
@Override public void getUserList(Callback callback) {
//doing some fake job
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
private final UserRepository.UserListCallback repositoryCallback =
new UserRepository.UserListCallback() {
@Override public void onUserListLoaded(Collection<User> usersCollection) {
notifyGetUserListSuccessfully(usersCollection);
}
// should this be in a new thread?
callback.onUserListLoaded(Collections.unmodifiableCollection(Collections.EMPTY_LIST));
@Override public void onError(ErrorBundle errorBundle) {
notifyError(errorBundle);
}
};
private void notifyGetUserListSuccessfully(final Collection<User> usersCollection) {
this.postExecutionThread.post(new Runnable() {
@Override public void run() {
callback.onUserListLoaded(Collections.unmodifiableCollection(usersCollection));
}
});
}
private void notifyError(final ErrorBundle errorBundle) {
this.postExecutionThread.post(new Runnable() {
@Override public void run() {
callback.onError(errorBundle);
}
});
}
}

View File

@ -5,13 +5,14 @@
package com.fernandocejas.android10.sample.domain.interactor;
/**
* Common interface for an interactor declared in the application.
* This interface represents a execution unit for different use cases.
* Common interface for an Interactor {@link java.lang.Runnable} declared in the application.
* This interface represents a execution unit for different use cases (this means any use case
* in the application should implement this contract).
*
* By convention each interactor implementation will return the result using a Callback should be
* executed in the UI thread.
* By convention each Interactor implementation will return the result using a Callback that should
* be executed in the UI thread.
*/
public interface Interactor {
public interface Interactor extends Runnable {
/**
* Everything inside this method will be executed asynchronously.
*/

View File

@ -17,7 +17,6 @@ public interface UserRepository {
*/
interface UserCallback {
void onUserLoaded(User user);
void onError(ErrorBundle errorBundle);
}
@ -25,8 +24,7 @@ public interface UserRepository {
* Callback used to be notified when either a user list has been loaded or an error happened.
*/
interface UserListCallback {
void onUserListLoaded(Collection<User> user);
void onUserListLoaded(Collection<User> usersCollection);
void onError(ErrorBundle errorBundle);
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (C) 2014 android10.org. All rights reserved.
* @author Fernando Cejas (the android10 coder)
*/
package com.fernandocejas.android10.sample.presentation;
import android.os.Handler;
import android.os.Looper;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
/**
* MainThread (UI Thread) implementation based on a Handler instantiated with the main
* application Looper.
*/
public class UIThread implements PostExecutionThread {
private static class LazyHolder {
private static final UIThread INSTANCE = new UIThread();
}
public static UIThread getInstance() {
return LazyHolder.INSTANCE;
}
private final Handler handler;
private UIThread() {
this.handler = new Handler(Looper.getMainLooper());
}
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the main thread.
*
* @param runnable {@link Runnable} to be executed.
*/
@Override public void post(Runnable runnable) {
handler.post(runnable);
}
}

View File

@ -1,34 +0,0 @@
/**
* Copyright (C) 2014 android10.org. All rights reserved.
* @author Fernando Cejas (the android10 coder)
*/
package com.fernandocejas.android10.sample.presentation.presenter;
import com.fernandocejas.android10.sample.domain.interactor.Interactor;
/**
* Abstract class representing a Presenter in a model view presenter (MVP) pattern.
*/
public abstract class BasePresenter {
/**
* Method that control the lifecycle of the view. It should be called in the view's
* (Activity or Fragment) onResume() method.
*/
public abstract void resume();
/**
* Method that control the lifecycle of the view. It should be called in the view's
* (Activity or Fragment) onPause() method.
*/
public abstract void pause();
/**
* Executes an {@link Interactor} asynchronously in a new thread.
* @param interactor The {@link Interactor} to execute.
*/
protected void executeInteractorAsync(Interactor interactor) {
if (interactor == null) {
throw new IllegalArgumentException("Interactor to execute cannot be null");
}
}
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (C) 2014 android10.org. All rights reserved.
* @author Fernando Cejas (the android10 coder)
*/
package com.fernandocejas.android10.sample.presentation.presenter;
/**
* Interface representing a Presenter in a model view presenter (MVP) pattern.
*/
public interface Presenter {
/**
* Method that control the lifecycle of the view. It should be called in the view's
* (Activity or Fragment) onResume() method.
*/
void resume();
/**
* Method that control the lifecycle of the view. It should be called in the view's
* (Activity or Fragment) onPause() method.
*/
void pause();
}

View File

@ -13,10 +13,7 @@ import com.fernandocejas.android10.sample.presentation.model.UserModel;
import com.fernandocejas.android10.sample.presentation.view.UserListView;
import java.util.Collection;
/**
*
*/
public class UserListPresenter extends BasePresenter {
public class UserListPresenter implements Presenter {
private final UserListView viewListView;
private final GetUserListUseCase getUserListUseCase;
@ -36,9 +33,7 @@ public class UserListPresenter extends BasePresenter {
this.loadUserList();
}
@Override public void pause() {
//nothing to do here
}
@Override public void pause() {}
/**
* Loads all users.
@ -78,7 +73,7 @@ public class UserListPresenter extends BasePresenter {
}
private void getUserList() {
this.getUserListUseCase.getUserList(userListCallback);
this.getUserListUseCase.execute(userListCallback);
}
private final GetUserListUseCase.Callback userListCallback = new GetUserListUseCase.Callback() {

View File

@ -24,7 +24,7 @@ public abstract class BaseFragment extends Fragment {
}
/**
* Initializes the {@link com.fernandocejas.android10.sample.presentation.presenter.BasePresenter}
* Initializes the {@link com.fernandocejas.android10.sample.presentation.presenter.Presenter}
* for this fragment in a MVP pattern used to architect the application presentation layer.
*/
abstract void initializePresenter();

View File

@ -13,12 +13,16 @@ import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper;
import com.fernandocejas.android10.sample.data.executor.JobExecutor;
import com.fernandocejas.android10.sample.data.repository.UserDataRepository;
import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory;
import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCase;
import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCaseImpl;
import com.fernandocejas.android10.sample.domain.repository.UserRepository;
import com.fernandocejas.android10.sample.presentation.R;
import com.fernandocejas.android10.sample.presentation.UIThread;
import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
import com.fernandocejas.android10.sample.presentation.model.UserModel;
import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter;
@ -65,10 +69,18 @@ public class UserListFragment extends BaseFragment implements UserListView {
// LEARNING EXAMPLE PURPOSE.
UserDataStoreFactory userDataStoreFactory = new UserDataStoreFactory(this.getContext());
UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();
UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,
userEntityDataMapper);
GetUserListUseCase getUserListUseCase = new GetUserListUseCaseImpl(userRepository);
ThreadExecutor threadExecutor = JobExecutor.getInstance();
PostExecutionThread postExecutionThread = UIThread.getInstance();
GetUserListUseCase getUserListUseCase = new GetUserListUseCaseImpl(userRepository,
threadExecutor, postExecutionThread);
UserModelDataMapper userModelDataMapper = new UserModelDataMapper();
this.userListPresenter = new UserListPresenter(this, getUserListUseCase, userModelDataMapper);
}