带测试的 MVP - 第 1 部分(MVP 架构)
MVP 架构
本教程包含有关 Android 应用程序架构原理的信息以及如何开发应用程序以结合 MVP 架构,从而改进和促进测试过程、代码可重用性、可扩展性以及 UI 组件与应用程序逻辑的分离。
Android 开发中的传统方法遵循MVC架构,如下所示
MVC
模型:存储数据,可能包含逻辑。
视图:视图仅仅是一个代表输出的虚拟占位符。
控制器:充当管理器,向用户提供响应并与用户界面交互。
Activity 或 Fragment 经常充当Controller 的角色,它包含了Sharedpreference的所有实现逻辑,网络回调,以及模型和数据库交互,同时还包括 Activity 生命周期回调。
最终,代码会急剧增长,维护、扩展和重构变得更加困难。MVC 与 Android API 紧密耦合,以至于逻辑实现无法通过单元测试进行测试,或者需要付出大量努力才能创建模拟测试环境。这就引出了下一种架构方法,即清洁架构
清洁架构
为了将逻辑与 Android API 分离,社区采用了干净的代码架构来创建分层架构;将模型、逻辑和用户界面分离在各个层中。
清晰的架构有助于实现代码
最有价值球员
为了解决架构问题,MVP 将应用程序分为三层,所有用户事件都委托给 Presenter。
- 模型:模型负责管理数据,与数据来源无关。模型的职责是缓存数据并验证数据源;无论是网络还是本地数据源,如 sqlite、文件等。模型通常通过存储库模式实现,以抽象出数据源的内部实现。
- View:View 的唯一任务是显示由 Presenter 指示的内容。View 接口可以由 Activity、Fragment 或其子类型实现。View 保留对 Presenter 的引用,以将用户事件委托给 Presenter 并执行面向 UI 的任务,例如填充列表、显示对话框或更新屏幕上的内容。
- Presenter:Presenter 是连接 Model 和 View 之间的通信桥梁。根据 View 传递的用户事件,Presenter 查询模型、转换数据,并将更新的数据传递给 View 以显示在屏幕上。
笔记:
实现基本 MVP 模式
该项目演示了从网络请求接收并显示电影列表的活动的工作(为了演示而在本地实现)。
这是项目结构的直观表示:
package com.example.pavneet_singh.mvptestingdemo.mvp.movieslist;
----------------------------------------------------------------
.model : POJO and Repository pattern
.mvp.movielist : MVP implementation for Movie List Screen
.util : To imitate a network response and provide helpful classes
- 实现被动视图:视图应实现为被动视图。MoviesListActivity实现MoviesListContract.View接口,以向演示者等其他组件公开功能。
// passive view
public class MoviesListActivity extends AppCompatActivity implements MoviesListContract.View{
Presenter 将保存对视图的引用并调用由 View 接口公开的方法。从数据源获取数据的过程将由 Presenter 处理。这种被动视图方法借助模拟对象简化了测试阶段。
- Android API Free Presenter:Presenter 处理来自 UI 的事件,并使用接口回调机制与长后台线程和进程进行通信。Presenter 实现了MoviesListContract.Presenter
public final class MoviePresenter implements MoviesListContract.Presenter {
制作可测试的演示者的关键点是避免使用 android API,因为通常常见的 android API(如Sharedpreference、数据库)需要上下文,而这些上下文在没有活动生命周期参与的情况下无法获取,因此要通过 IDE 上的 JVM 进行本地 junit 测试,需要使用mockito框架来创建虚拟对象。
- View 和 Presenter 的完整契约:View和Presenter接口被组合成一个单独的接口,称为Contract,该接口将用于在View和Presenter的具体实现之间建立关系。
电影列表合同.java
public interface MoviesListContract {
// implemented by MoviesListActivity to provide concrete implementation
interface View {
void showProgress(); // display progress bar
void hideProgress(); // hide progress bar
void showMovieList(List<Movie> movies); // receive response to display
void showLoadingError(String errMsg); // display error
}
// implemented by MoviesPresenter to handle user event
interface Presenter{
void loadMoviewList();
void dropView();
}
// implemented by MoviePresenter to receive response from asynchronous processes
interface OnResponseCallback{
void onResponse(List<Movie> movies);
void onError(String errMsg);
}
}
View接口只是指示 UI 行为,而Presenter的具体实现将通过调用View的方法来管理 UI 的状态。
与面向结果的数据操作相比,Presenter 具有更多针对用户操作的特定方法。Presenter 还使用OnResponseCallback接口与后台任务和进程建立通信链接。
将 Presenter 和 View 保留在单个界面中的原因是
View也是 Android 中的一个类。因此,我们可以将界面命名为View,而不会造成任何混淆。
视图和演示器功能均可在一个地方使用,从而提高了清晰度。
Presenter 和 View 都具有服务于单个实体(活动或片段)的特定行为,因此将相同的东西放在一起更有意义。
制作演示者时的重要点
- 不要在 Presenter 中创建生命周期,否则你的 Presenter 将与 Android 组件紧密相连,而 Android 组件之间又存在很大差异。这会使 Presenter 的依赖性更强,并且修改起来非常困难。
- 在演示者中引入一个接受视图引用的方法需要在很多地方进行空检查,因此如果可以的话请避免这样做而使用构造函数。
- 使用类似LRUCache的缓存机制来保留数据,保持presenter无状态,并让它在activity或者fragment创建的时候重新创建。
- 连接中的 View 和 Presenter:MoviePresenter将通过构造函数接收View的引用,即MoviesListActivity,以及MovieRepo接口的具体实现,该接口将包含从数据源(网络、本地数据库、文件等)检索数据的逻辑。
下面是演示者的具体实现,即MoviePresenter.java:
public final class MoviePresenter implements MoviesListContract.Presenter {
// to keep reference to view
private MoviesListContract.View view;
// Repository pattern, mclient holds reference to concrete data retrieval implementation
private MovieRepo mclient;
public MoviePresenter(MoviesListContract.View view,MovieRepo client) {
this.view = view;
mclient = client;
}
@Override
public void dropView() {
view = null;
}
// would be triggered by MovieListActivity
@Override
public void loadMoviewList() {
view.showProgress();
mclient.getMovieList(callback);
// required for espresso UI testing
// to wait till response occurred
EspressoTestingIdlingResource.increment();
}
// callback mechanism , onResponse will be triggered with response
// by simulatemovieclient(or your network or database process) and pass the response to view
private final OnResponseCallback callback = new OnResponseCallback() {
@Override
public void onResponse(List<Movie> movies) {
view.showMovieList(movies);
view.hideProgress();
EspressoTestingIdlingResource.decrement();
}
@Override
public void onError(String errMsg) {
view.hideProgress();
view.showLoadingError(errMsg);
EspressoTestingIdlingResource.decrement();
}
};
}
当用户执行操作(在屏幕上向下滑动)时, MoviesListActivity将触发loadMoviewList方法,然后屏幕上将显示一个进度条,直到收到响应并通过view.showMovieList(movies);方法传递给活动以显示列表。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~