Android開發MVP模式實踐
現在用一個基于MVP模式的APP項目進一步分析MVP的實際應用。
原項目應該使用的是Android studio開發,筆者對項目進行了整理,廣大Eclipser請猛點Github鏈接。
一、項目功能說明
APP獲取好友列表后將數據展示在一個ListView中,點擊Item會打開一個新頁面展示好友詳細信息。
二、項目結構
示例將代碼分為四層,對應到MVP模式中:
- Mode:Entities
- Presenter:Use Cases+Presenters
- View:UI
為了保證每個層都能方便的進行單元測試并和其它層相對獨立,將項目分為三個模塊,Presentation、Domain和Data。
1)Presentation
Presenters和UI被劃分到這一層,但Presenters在這里只是負責將Domain邏輯處理后的數據進行組裝并調度UI顯示,沒有業務處理邏輯。這樣將數據邏輯處理劃分給Domain可以讓Presenter更關注于UI顯示的調度,從而避免Present邏輯的冗余。這也是我選擇這個工程作為示例的原因。
2)Domain
這里對Data中的數據進行邏輯處理,為Present提供業務邏輯和數據支持。
3)Data
數據倉庫。例如,當通過id獲取用戶數據時,首先會檢測用戶信息是否已經存儲在本地,否則的話就會從服務器獲取后在本地緩存。根據上篇博客提到的設計原則,外圓代碼邏輯無需關心用戶數據是從存儲介質、內存還是服務器獲取,只需拿到Domain處理好的最終數據進行展示和交互即可。
根據以上說明,筆者將APP分為三個工程
其中Domain和Data作為Library的形式供Presentation引入。
三、代碼詳解
通過獲取用戶詳細信息這個功能分析各個層級之間的調度及數據傳遞方向和方式。
首先入口View層的UserDetaisFragment。
public class UserDetailsFragment extends BaseFragment implements UserDetailsView
private UserDetailsPresenter userDetailsPresenter;
@Override void initializePresenter() {
// All these dependency initialization could have been avoided using a
// dependency injection framework. But in this case are used this way for
// LEARNING EXAMPLE PURPOSE.
ThreadExecutor threadExecutor = JobExecutor.getInstance();
PostExecutionThread postExecutionThread = UIThread.getInstance();
JsonSerializer userCacheSerializer = new JsonSerializer();
UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer,
FileManager.getInstance(), threadExecutor);
UserDataStoreFactory userDataStoreFactory =
new UserDataStoreFactory(this.getContext(), userCache);
UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();
UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,
userEntityDataMapper);
GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository,
threadExecutor, postExecutionThread);
UserModelDataMapper userModelDataMapper = new UserModelDataMapper();
this.userDetailsPresenter =
new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper);
}
UserDetailsFragment內部有個UserDetailsPresenter引用,下面我們看UserDetailsPresenter代碼。
public class UserDetailsPresenter implements Presenter {
/** id used to retrieve user details */
private int userId;
private final UserDetailsView viewDetailsView;
private final GetUserDetailsUseCase getUserDetailsUseCase;
private final UserModelDataMapper userModelDataMapper;
public UserDetailsPresenter(UserDetailsView userDetailsView,
GetUserDetailsUseCase getUserDetailsUseCase, UserModelDataMapper userModelDataMapper) {
if (userDetailsView == null || getUserDetailsUseCase == null || userModelDataMapper == null) {
throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
}
this.viewDetailsView = userDetailsView;
this.getUserDetailsUseCase = getUserDetailsUseCase;
this.userModelDataMapper = userModelDataMapper;
}
private void showViewLoading() {
this.viewDetailsView.showLoading();
}
private void getUserDetails() {
this.getUserDetailsUseCase.execute(this.userId, this.userDetailsCallback);
}
private final GetUserDetailsUseCase.Callback userDetailsCallback = new GetUserDetailsUseCase.Callback() {
@Override public void onUserDataLoaded(User user) {
UserDetailsPresenter.this.showUserDetailsInView(user);
UserDetailsPresenter.this.hideViewLoading();
}
@Override public void onError(ErrorBundle errorBundle) {
UserDetailsPresenter.this.hideViewLoading();
UserDetailsPresenter.this.showErrorMessage(errorBundle);
UserDetailsPresenter.this.showViewRetry();
}
};
}
private void hideViewLoading() {
this.viewDetailsView.hideLoading();
}
private void showViewRetry() {
this.viewDetailsView.showRetry();
}
private void hideViewRetry() {
this.viewDetailsView.hideRetry();
}
Presenter包涵一個GetUserDetailsUseCase引用,并在調用getUserDetails()方法時傳入一個callBack作為數據處理結果的回調。可以看到Presenter中并沒有復雜的邏輯處理,反而更多的是hideViewRetry(),showViewLoading()等對Fragemnt的顯示調度方法。Fragemt實現了UserDetailsView接口協議并在實例化Presenter時當做參數傳入,這樣就搭建好了符合MVP模式的Presenter和View的交互方式。
public interface UserDetailsView extends LoadDataView {
/**
* Render a user in the UI.
*
* @param user The {@link UserModel} that will be shown.
*/
void renderUser(UserModel user);
}
public interface LoadDataView {
/**
* Show a view with a progress bar indicating a loading process.
*/
void showLoading();
/**
* Hide a loading view.
*/
void hideLoading();
/**
* Show a retry view in case of an error when retrieving data.
*/
void showRetry();
/**
* Hide a retry view shown if there was an error when retrieving data.
*/
void hideRetry();
/**
* Show an error message
*
* @param message A string representing an error.
*/
void showError(String message);
/**
* Get a {@link android.content.Context}.
*/
Context getContext();
}
下面我們看GetUserDetailUseCase代碼。GetUserDetailUseCase在Domain模塊,負責加工處理Data模塊的數據。
public class GetUserDetailsUseCaseImpl implements GetUserDetailsUseCase {
private final UserRepository userRepository;
@Override public void execute(int userId, Callback callback) {
if (userId < 0 || callback == null) {
throw new IllegalArgumentException("Invalid parameter!!!");
}
this.userId = userId;
this.callback = callback;
this.threadExecutor.execute(this);
}
@Override public void run() {
this.userRepository.getUserById(this.userId, this.repositoryCallback);
}
private final UserRepository.UserDetailsCallback repositoryCallback =
new UserRepository.UserDetailsCallback() {
@Override public void onUserLoaded(User user) {
notifyGetUserDetailsSuccessfully(user);
}
@Override public void onError(ErrorBundle errorBundle) {
notifyError(errorBundle);
}
};
private void notifyGetUserDetailsSuccessfully(final User user) {
this.postExecutionThread.post(new Runnable() {
@Override public void run() {
callback.onUserDataLoaded(user);
}
});
}
}
GetUserDetailUseCaseImpl又會調用Data模塊的UserRepository獲取用戶數據。UserRepository會從本地存儲或服務器獲取用戶數據,這里就不再跟進UserRepository的源碼。
當GetUserDetailUseCaseImpl從UserRepository獲取用戶數據后通過UserRepository.UserDetailCallback回調給UserDetailPresenter,然后Presenter就會根據數據調度Fragment的顯示。
下面總結下從用戶點擊Item到打開用戶詳情頁的調用流程及數據流動方向
用戶點擊Fragment中ListView的Item,Fragment向Presentor詢問用戶詳情信息,Presentor將命令傳遞給UseCase,UseCase從Data獲取數據并加工后通過callBack將數據傳遞回Presentor,Presentor最終告訴Fragment打開新頁面并展示數據。
大家下載源碼后會發現甭管是Presentor和UI,還是UseCase和Data,都是通過接口協議進行交互,這也是MVP模式的特點之一。
MVP就分析到這里,我認識的也很有限,歡迎大家討論指正。
參考資料:
MVC or MVP Pattern - Whats the difference?
來自: http://blog.csdn.net/guxiao1201/article/details/40151457