背景知识
什么是todo?
todo就是简单的记事app
什么是MVP?
网上有很多概念,我也看了很多,一直只有一个模糊的印象。最近通过分析和模仿todo-mvp写了个demo有了完全不同的理解。
Model:提供数据,从本地或从云端
View:View即视图
Presenter:负责逻辑的处理,从Model中取数据,然后处理,传给View层,View将其展示
开始分析todo-MVP
项目结构
data包为整个app提供数据包括local和remote,local包下就是封装了对数据库的操作,这里我们重点关注的是TasksRepository,它封装了数据的获取,从本地或者云端。
tasks包和taskdetail以及addedittask打开后结构是一样的,都包含一个activity一个fragment一个contract一个presener,所以我们只以task包为例分析,既最上面的那张图。
先来看看各个不同的包和不同的接口是怎么配合的。
Repository为M,Presenter为P,View为V
在这之前我们先来说说接口BaseView和BasePresenter,BaseView中只有一个setPresenter(),BasePresenter只有一个star()
然后看看TasksContract`
public interface TasksContract {
interface View extends BaseView<Presenter> {
....
void showTasks(List<Task> tasks);
void showLoadingTasksError();
void showNoTasks();
....
}
interface Presenter extends BasePresenter {
....
void loadTasks(boolean forceUpdate);
void openTaskDetails(@NonNull Task requestedTask);
void clearCompletedTasks();
....
}
}
contract中有两个接口分别实现BaseView和BasePresenter,Presenter中在加入在Tasks中可能发生的操作,BaseView中加入View的响应操作。
View
接下来看看Fragment即View
public class TasksFragment extends Fragment implements TasksContract.View {
....
private TasksContract.Presenter mPresenter;
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void showTasks(List<Task> tasks) {
mListAdapter.replaceData(tasks);
mTasksView.setVisibility(View.VISIBLE);
mNoTasksView.setVisibility(View.GONE);
}
@Override
public void showNoTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_all),
R.drawable.ic_assignment_turned_in_24dp,
false
);
}
private void showNoTasksViews(String mainText, int iconRes, boolean showAddView) {
mTasksView.setVisibility(View.GONE);
mNoTasksView.setVisibility(View.VISIBLE);
mNoTaskMainView.setText(mainText);
mNoTaskIcon.setImageDrawable(getResources().getDrawable(iconRes));
mNoTaskAddView.setVisibility(showAddView ? View.VISIBLE : View.GONE);
}
....
}
注意在OnResume中调用了star(),一般就是从Repository中获取数据,View层根据接口返回的数据进行UI操作,同时根据监听事件调用Presenter相应的方法(presenter中有对View接口的引用,之后会回调)
Presenter
public class TasksPresenter implements TasksContract.Presenter {
....
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
}
@Override
public void start() {
loadTasks(false);
}
@Override
public void loadTasks(boolean forceUpdate) {
// Simplification for sample: a network reload will be forced on first load.
loadTasks(forceUpdate || mFirstLoad, true);
mFirstLoad = false;
}
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
if (forceUpdate) {
mTasksRepository.refreshTasks();
}
// The network request might be handled in a different thread so make sure Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
List<Task> tasksToShow = new ArrayList<Task>();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
EspressoIdlingResource.decrement(); // Set app as idle.
}
// We filter the tasks based on the requestType
for (Task task : tasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToShow.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToShow.add(task);
}
break;
default:
tasksToShow.add(task);
break;
}
}
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasksToShow);
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
mTasksView.showLoadingTasksError();
}
});
}
@Override
public void openTaskDetails(@NonNull Task requestedTask) {
checkNotNull(requestedTask, "requestedTask cannot be null!");
mTasksView.showTaskDetailsUi(requestedTask.getId());
}
@Override
public void completeTask(@NonNull Task completedTask) {
checkNotNull(completedTask, "completedTask cannot be null!");
mTasksRepository.completeTask(completedTask);
mTasksView.showTaskMarkedComplete();
loadTasks(false, false);
}
....
}
在TasksPresenter中在实例化的时候持有了对Repository和TaskContract.View的引用,并且调用setPresenter()给TasksContract.View设置Presenter。当View层调用Presenter的方法时他进行相应的对Repository的操作,完成后根据情况回调TaskContract.View的相应方法。
Model
首先我们先想想需要对数据做什么操作,就todo而言,一般就是需要获取Tasks,删除Task等等,所以我们先抽象出一个接口集
public interface TasksDataSource {
....
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
void getTasks(@NonNull LoadTasksCallback callback);
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
void saveTask(@NonNull Task task);
void deleteAllTasks();
void deleteTask(@NonNull String taskId);
....
}
内部的两个接口是用来将数据回调到外部的,现在来看看repository的实现
public class TasksRepository implements TasksDataSource {
....
private static TasksRepository INSTANCE = null;
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
getTasksFromRemoteDataSource(callback);
} else {
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
}
....
}
从以上可知Repository中,它的作用是给Presenter提供数据,而它来处理从缓存还是从本地数据库或者从网络请求的逻辑操作,它持有一个TasksLocalDataSource和TasksRemoteDataSource的引用,而真正对数据库和网络数据操作的的就是TasksLocalDataSource和TasksRemoteDataSource,他们同样实现了接口TasksDataSource,具体代码可以打开链接查看。
最后我们再来理一下各个类间的关系,TasksPresenter持有TasksContract.View和Repository的引用,实现了TasksContract.View的Fragment持有Presenter的引用,Repository持有TasksLocalDataSoure和TasksRemoteDataSource的引用。
最后我们需要一个Activity来把这所有东西调动以来
public class TasksActivity extends AppCompatActivity {
private TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter( Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
}
}
现在整个MVP就完整了。第一次认真写博客,请多包涵。