diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java b/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java new file mode 100644 index 0000000..81c146e --- /dev/null +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java @@ -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 workQueue; + + private final ThreadPoolExecutor threadPoolExecutor; + + private JobExecutor() { + this.workQueue = new LinkedBlockingQueue(); + 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); + } +} diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/MainThread.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java similarity index 55% rename from domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/MainThread.java rename to domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java index 214403c..b3cfeae 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/MainThread.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java @@ -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); diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/Executor.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/ThreadExecutor.java similarity index 73% rename from domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/Executor.java rename to domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/ThreadExecutor.java index bc42664..40c02ab 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/Executor.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/ThreadExecutor.java @@ -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. diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCase.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCase.java index aeb674d..15e18ee 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCase.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCase.java @@ -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 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); } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCaseImpl.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCaseImpl.java index d8d4831..2c35414 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCaseImpl.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCaseImpl.java @@ -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 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 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); + } + }); } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/Interactor.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/Interactor.java index edfafa2..e944baf 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/Interactor.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/Interactor.java @@ -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. */ diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java index 296d36c..7a4b2fa 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java @@ -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); - + void onUserListLoaded(Collection usersCollection); void onError(ErrorBundle errorBundle); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java new file mode 100644 index 0000000..08de80b --- /dev/null +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java @@ -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); + } +} diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/BasePresenter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/BasePresenter.java deleted file mode 100644 index 44650a6..0000000 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/BasePresenter.java +++ /dev/null @@ -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"); - } - } -} diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/Presenter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/Presenter.java new file mode 100644 index 0000000..49ab98c --- /dev/null +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/Presenter.java @@ -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(); +} diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java index 4abff7a..029f7c2 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java @@ -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() { diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java index fd9157e..8d0ad0b 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java @@ -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(); diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java index 8f1020e..45cebab 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java @@ -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); }