preface
Before this project refactoring, what was the architecture of my project?
Well, no structure… Or, less standard MVC, a page is an Activity or Fragment, and all the data, all the network requests, all the responses are written in the Activity or Fragment– these two less standard controllers, the code is messy and it’s long and it’s long. When I was writing code, when I was in maintenance, it was so cool. I can’t believe it. The adoption of a new architecture is urgent.
In fact, there are many App projects that use MVP or MVVM architectures. Without such an authoritative implementation, many developers fall into the dilemma of choosing between architectures, unable to find the right one for their project. Google duly released a series of official examples for reference.
Google Sample Project
Android Architecture Blueprints [beta]
The sample project, which uses a TODO APP as an example, is still ongoing. The basic todo-MVP was used for this project refactoring
The code organization of the sample project is different from the way I organized adapter, Activity, fragment and so on. It is divided according to functions. One function is a package. The files in the package are named xxxActivity, xxxFragment, xxxContract, and xxxPresenter, with XXX representing the function to be implemented. Both methods were adopted in this project reconstruction.
refactoring
The first is to create two Base interfaces, BaseView and BasePresenter, modeled after TODO MVP. These two Base interfaces are the Base classes for all Views and Presenters.
public interface BasePresenter {
void start();
}Copy the code
BasePresenter has the start() method, which is used by the Presenter to retrieve data and change the display, calling onResume() in the Fragment method.
public interface BaseView {
void setPresenter(T presenter);
void initViews(View view);
}Copy the code
SetPresenter () is a BaseView method that passes the Presenter example to the View. In the constructor of the Presenter implementation class, initViews() is passed to the View instance and is used to initialize interface elements. Call onCreate() method with Fragment timing.
Next, create a contract class that manages all interfaces to View and Presenter in a unified manner. Here, part of Zhihu Daily is taken as an example.
public interface ZhihuDailyContract { interface View extends BaseView { void showError(); void showLoading(); void stopLoading(); void showResults(ArrayList list); void showNetworkError(); } interface Presenter extends BasePresenter { void loadPosts(long date, boolean clearing); void refresh(); void loadMore(long date); void startReading(int position); void goToSettings(); }}Copy the code
The corresponding Activity is then created. In the official example project, the Activity is used as a bridge between View and Presenter to create instances of the View and Presenter. This project involves the use of TabLayout and ViewPager, so I create View and Presenter part into the Adapter of ViewPager implementation.
public class MainPagerAdapter extends FragmentPagerAdapter { private String[] titles; private final Context context; public MainPagerAdapter(FragmentManager fm, Context context) { super(fm); this.context = context; titles = context.getResources().getStringArray(R.array.page_titles); } @Override public Fragment getItem(int position) { if (position == 1){ GuokrFragment fragment = GuokrFragment.newInstance(); new GuokrPresenter(context, fragment); return fragment; } else if (position == 2){ DoubanMomentFragment fragment = DoubanMomentFragment.newInstance(); new DoubanMomentPresenter(context, fragment); return fragment; } ZhihuDailyFragment fragment = ZhihuDailyFragment.newInstance(); new ZhihuDailyPresenter(context, fragment); return fragment; } @Override public int getCount() { return titles.length; } @Override public CharSequence getPageTitle(int position) { return titles[position]; }}Copy the code
The Fragment’s role in the sample project is the concrete implementation class of the View.
public class ZhihuDailyFragment extends Fragment implements ZhihuDailyContract.View{ private RecyclerView recyclerView; private SwipeRefreshLayout refresh; private FloatingActionButton fab; private ZhihuDailyNewsAdapter adapter; private ZhihuDailyContract.Presenter presenter; public ZhihuDailyFragment() { } public static ZhihuDailyFragment newInstance() { return new ZhihuDailyFragment(); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_douban_zhihu_daily,container,false); initViews(view); presenter.loadPosts(Calendar.getInstance().getTimeInMillis(), false); refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { presenter.refresh(); }}); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ... }}); return view; } @Override public void setPresenter(ZhihuDailyContract.Presenter presenter) { if (presenter ! = null) { this.presenter = presenter; } } @Override public void initViews(View view) { recyclerView = (RecyclerView) view.findViewById(R.id.rv_main); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),LinearLayoutManager.VERTICAL)); refresh = (SwipeRefreshLayout) view.findViewById(R.id.refresh); fab = (FloatingActionButton) view.findViewById(R.id.fab); fab.setRippleColor(getResources().getColor(R.color.colorPrimaryDark)); } @Override public void showError() { Snackbar.make(fab, R.string.loaded_failed,Snackbar.LENGTH_SHORT).show(); } @Override public void showLoading() { refresh.post(new Runnable() { @Override public void run() { refresh.setRefreshing(true); }}); } @Override public void stopLoading() { refresh.post(new Runnable() { @Override public void run() { refresh.setRefreshing(false); }}); } @Override public void showResults(ArrayList list) { if (adapter == null) { adapter = new ZhihuDailyNewsAdapter(getContext(), list); adapter.setItemClickListener(new OnRecyclerViewOnClickListener() { @Override public void OnItemClick(View v, int position) { presenter.startReading(position); }}); recyclerView.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); } } @Override public void showNetworkError() { Snackbar.make(fab,R.string.no_network_connected,Snackbar.LENGTH_INDEFINITE) .setAction(R.string.go_to_set, new View.OnClickListener() { @Override public void onClick(View v) { presenter.goToSettings(); } }).show(); }}Copy the code
To create a Presenter.
public class ZhihuDailyPresenter implements ZhihuDailyContract.Presenter, OnStringListener {
private ZhihuDailyContract.View view;
private Context context;
private StringModelImpl model;
private ArrayList list = new ArrayList();
public ZhihuDailyPresenter(Context context, ZhihuDailyContract.View view) {
this.context = context;
this.view = view;
this.view.setPresenter(this);
model = new StringModelImpl(context);
}
@Override
public void loadPosts(long date, boolean clearing) {
view.showLoading();
if (clearing) {
list.clear();
}
model.load(Api.ZHIHU_HISTORY + formatter.ZhihuDailyDateFormat(date), this);
}
@Override
public void refresh() {
list.clear();
loadPosts(Calendar.getInstance().getTimeInMillis(), true);
}
@Override
public void loadMore(long date) {
if (NetworkState.networkConnected(context)) {
model.load(Api.ZHIHU_HISTORY + formatter.ZhihuDailyDateFormat(date), this);
} else {
view.showNetworkError();
}
}
@Override
public void startReading(int position) {
context.startActivity(new Intent(context, ZhihuDetailActivity.class)
.putExtra("id",list.get(position).getId())
);
}
@Override
public void goToSettings() {
context.startActivity(new Intent(Settings.ACTION_SETTINGS));
}
@Override
public void start() {
}
@Override
public void onSuccess(String result) {
Gson gson = new Gson();
ZhihuDailyNews post = gson.fromJson(result, ZhihuDailyNews.class);
for (ZhihuDailyNews.Question item : post.getStories()) {
list.add(item);
}
view.showResults(list);
view.stopLoading();
}
@Override
public void onError(VolleyError error) {
view.stopLoading();
view.showError();
}
}Copy the code
Presenter gets the View and passes it in by calling setPresenter(). If you need to change the presentation of the interface, you can call the View layer methods directly. The Presenter is thus separated from the View layer.
Finally, the implementation of the Model layer. Because of Gson, the return type of the data only needs to be String.
Public interface OnStringListener {/** * @param result */ void onSuccess(String result); /** * Callback * @param error */ void onError(VolleyError error); }Copy the code
Two methods are defined as a callback when the request succeeds and when the request fails.
Then you need an implementation class for StringModel –StringModelImpl.
public class StringModelImpl { private Context context; public StringModelImpl(Context context) { this.context = context; } public void load(String url, final OnStringListener listener) { StringRequest request = new StringRequest(url, new Response.Listener() { @Override public void onResponse(String s) { listener.onSuccess(s); }}, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onError(volleyError); }}); VolleySingleton.getVolleySingleton(context).addToRequestQueue(request); }}Copy the code
In this way, Model, View, and Presenter are all implemented, achieving the separation of the various levels.
Refactoring with the MVP architecture increases the amount of code compared to the original project, but the benefit of this increase relative to the MVP architecture is obvious. Of course, this is for the large amount of code for the project, usually used for training small projects there is no need to strong project difficult, reluctantly to achieve MVP, this will only increase the amount of code.
Project Address
Paper Plane – With MVP architecture, it is a comprehensive reading client integrating Zhihu Daily newspaper, Guoba Selection and Douban Moment