Android MVP进阶:“修行在个人”

距离上一篇Android MVP从懵逼到入门:登陆业务实践已经有一段时间了,这段时间忙着公司的项目,都没找时间写写文章,今天就把这段时间整理的MVP知识再总结一下,这篇文章主要介绍我是如何使用MVP模式来实现主页多个Tab切换的场景的。

说是复杂,其实只是多了一些Fragment而已,在上一篇文章中,我们说Activity的主要作用是创建view和presenter,并把view实例传入到presenter中,那么,对于多个Tab切换的主页面(现在的大多数app都是这样的主页),Activity的作用就又多了一个,也是大家都知道的:管理Fragment。其实这没什么想不到的,在没说MVP的时候,大家也都是这么做的。

这里我参考网上网友收集的知乎日报的相关api,使用mvp模式简单的实现了知乎日报主页的内容。先看一下效果:


主页效果图
主页效果图

presenter

先看看presenter怎么写,应该说跟上一篇的实现是一样的,那就得先写好契约类--MainContact.java:

public interface MainContract { 
    interface Presenter extends BasePresenter { 
        RootEntity getLatestNews(); 
        RootEntity getSafety(); 
        RootEntity getInterest(); 
        RootEntity getSport(); 
    } 
    interface View extends BaseView<Presenter> { 
        void setTitle(); 
        void refresh(List<StoriesEntity> list); 
    }
}

写好契约类,那么这个模块有哪些主要功能基本都清楚了,看view接口,可以知道,页面可以实现的功能有:

  • 1.设置页面标题--setTitle
  • 2.刷新页面内容--refresh

看presenter接口,可以知道,model提供了那些数据访问接口:- 1.获取今日日报模块内容- 2.获取网络安全模块内容- 3.获取不许无聊模块内容- 4.获取体育日报模块内容契约类写好后,就可以实现presenter接口了,看看MainPresenter.java类:

public class MainPresenter implements MainContract.Presenter {

    private String baseUrl = "http://news-at.zhihu.com";

    private MainContract.View mMainView;
    private Context mContext;

    protected ZhiHuService service;

    public MainPresenter(Context context) {
        this.mContext = context;
    }

    public void setView(MainContract.View view) {
        this.mMainView = view;
        mMainView.setPresenter(this);
        mMainView.setTitle();
        service = getService();
    }

    public ZhiHuService getService() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
        service = retrofit.create(ZhiHuService.class);
        return service;
    }

    @Override
    public RootEntity getLatestNews() {
        return loadData(service.getLatestNews());
    }

    @Override
    public RootEntity getSafety() {
        return loadData(service.getSafety());
    }

    @Override
    public RootEntity getInterest() {
        return loadData(service.getInterest());
    }

    @Override
    public RootEntity getSport() {
        return loadData(service.getSport());
    }

    @Override
    public void start() {

    }

    public RootEntity loadData(Observable<RootEntity> observable) {
        final RootEntity rootEntity = new RootEntity();
        observable.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .map(new Func1<RootEntity, ArrayList<StoriesEntity>>() {
                    @Override
                    public ArrayList<StoriesEntity> call(RootEntity rootEntity) {
                        return rootEntity.getStories();
                    }
                })
                .subscribe(new Subscriber<ArrayList<StoriesEntity>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(ArrayList<StoriesEntity> storiesEntities) {
                        rootEntity.setStories(storiesEntities);
                        mMainView.refresh(storiesEntities);
                    }
                });

        return rootEntity;
    }
}

view

view其实才是这篇文章的主题,契约类里面定义的一些接口其实是所有的view实现类的集合,比如这里有4个Fragment页面,每个页面展示的内容是不一样的,需要的接口也不一样,这些接口都需要在契约类里面定义,然后每个Fragment都去实现这个接口,完成对应接口中的内容。

比如这4个Fragment都去实现MainContract.View,各自都实现setTitle和refresh内容,如何还有其他需要实现的接口,同样定义在契约类里面,谁关心这个接口,谁就去实现这个接口,view不必关心接口的调用,只需要实现内容就行了,这在MVP中体现的比较明显:面向接口编程。(我所接触的面向接口编程的另一种场景就是模块化编程,感觉有些类似。)

既然有4个Fragment,那么他们肯定有共同之处,至少他们都是Fragment,何不提取基类呢?这相比大家都能想到--Java语言的三大特性即:封装、继承、多态

来看看BaseFragment的内容:

public class BaseFragment extends Fragment implements MainContract.View {

    @BindView(R.id.lv_news)
    ListView mListView;

    protected MainContract.Presenter mPresenter;
    protected ActionBar mActionBar;
    private ZhiHuNewsAdapter mAdapter;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main_0, container, false);
        ButterKnife.bind(this, view);

        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        mAdapter = new ZhiHuNewsAdapter(getContext());
        mListView.setAdapter(mAdapter);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (isAdded()) {
            mActionBar = ((AppCompatActivity)getActivity()).getSupportActionBar();
            setTitle();
        }
    }

    @Override
    public void setPresenter(MainContract.Presenter presenter) {
        this.mPresenter = presenter;
    }

    @Override
    public void refresh(final List<StoriesEntity> list) {
        mAdapter.setNewsList(list);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(getActivity(), StoryDetailActivity.class);
                intent.putExtra(StoryDetailFragment.STORY_ID, list.get(position).getId());
                intent.putExtra(StoryDetailFragment.STORY_TITLE, list.get(position).getTitle());
                startActivity(intent);
            }
        });
    }

    @Override
    public void setTitle() {
        if (mActionBar != null) {
            mActionBar.setTitle(R.string.app_name);
        }
    }
}

model

因为这里没有考虑到数据的持久化,没有创建本地数据库,所以,model的实现其实就是一个service,因为是用reftrofit2来完成网络请求的,所以,model就是ZhiHuService.java:

public interface ZhiHuService {

    //今日头条
    @GET("/api/4/news/latest")
    Observable<RootEntity> getLatestNews();

    //互联网安全
    @GET("/api/4/theme/10")
    Observable<RootEntity> getSafety();

    //不准无聊
    @GET("/api/4/theme/11")
    Observable<RootEntity> getInterest();

    //体育日报
    @GET("/api/4/theme/8")
    Observable<RootEntity> getSport();
}

avtivity

最后看看activity,作用有三个:创建view(这里是views)、创建presenter、管理fragemnt:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tab_item_main_0)
    TabItem tabItemMain0;

    @BindView(R.id.tab_item_main_1)
    TabItem tabItemMain1;

    @BindView(R.id.tab_item_main_2)
    TabItem tabItemMain2;

    @BindView(R.id.tab_item_main_3)
    TabItem tabItemMain3;

    @BindView(R.id.tab_item_main_4)
    TabItem tabItemMain4;

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.fab)
    FloatingActionButton floatingBar;

    private FragmentManager mFragmentManager;
    private TodayFragment mTodayFragment;
    private InterestFragment mInterestFragment;
    private SafetyFragment mSafetyFragment;
    private SportFragment mSportFragment;
    private OtherFragment mOtherFragment;

    private MainPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mFragmentManager = getSupportFragmentManager();
        setSupportActionBar(toolbar);

        mPresenter = new MainPresenter(getApplicationContext());

        tabItemMain0.performClick();
    }

    public void showFragment(int tag) {
        if (mFragmentManager != null) {
            FragmentTransaction transaction = mFragmentManager.beginTransaction();
            hideFragments();
            switch (tag) {
                case TagStatic.TAG_FRAGMENT_TODAY:
                    mTodayFragment = (TodayFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_TODAY + "");
                    if (mTodayFragment == null) {
                        mTodayFragment = new TodayFragment();
                        transaction.add(R.id.fragment_content, mTodayFragment, tag + "");
                    } else {
                        transaction.show(mTodayFragment);
                    }
                    mPresenter.setView(mTodayFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_INTEREST:
                    mInterestFragment = (InterestFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_INTEREST + "");
                    if (mInterestFragment == null) {
                        mInterestFragment = new InterestFragment();
                        transaction.add(R.id.fragment_content, mInterestFragment, tag + "");
                    } else {
                        transaction.show(mInterestFragment);
                    }
                    mPresenter.setView(mInterestFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_SAFETY:
                    mSafetyFragment = (SafetyFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SAFETY + "");
                    if (mSafetyFragment == null) {
                        mSafetyFragment = new SafetyFragment();
                        transaction.add(R.id.fragment_content, mSafetyFragment, tag + "");
                    } else {
                        transaction.show(mSafetyFragment);
                    }
                    mPresenter.setView(mSafetyFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_SPORT:
                    mSportFragment = (SportFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SPORT + "");
                    if (mSportFragment == null) {
                        mSportFragment = new SportFragment();
                        transaction.add(R.id.fragment_content, mSportFragment, tag + "");
                    } else {
                        transaction.show(mSportFragment);
                    }
                    mPresenter.setView(mSportFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_OTHER:
                    mOtherFragment = (OtherFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_OTHER + "");
                    if (mOtherFragment == null) {
                        mOtherFragment = new OtherFragment();
                        transaction.add(R.id.fragment_content, mOtherFragment, tag + "");
                    } else {
                        transaction.show(mOtherFragment);
                    }
                    mPresenter.setView(mOtherFragment);
                    break;
            }

            transaction.commitAllowingStateLoss();
        }
    }

    private void hideFragments() {
        FragmentTransaction transaction = mFragmentManager.beginTransaction();
        mTodayFragment = (TodayFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_TODAY + "");
        if (mTodayFragment != null) {
            transaction.hide(mTodayFragment);
        }

        mInterestFragment = (InterestFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_INTEREST + "");
        if (mInterestFragment != null) {
            transaction.hide(mInterestFragment);
        }

        mSafetyFragment = (SafetyFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SAFETY + "");
        if (mSafetyFragment != null) {
            transaction.hide(mSafetyFragment);
        }

        mSportFragment = (SportFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SPORT + "");
        if (mSportFragment != null) {
            transaction.hide(mSportFragment);
        }

        mOtherFragment = (OtherFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_OTHER + "");
        if (mOtherFragment != null) {
            transaction.hide(mOtherFragment);
        }

        transaction.commitAllowingStateLoss();
    }

    @OnClick({R.id.tab_item_main_0, R.id.tab_item_main_1, R.id.tab_item_main_2,
            R.id.tab_item_main_3, R.id.tab_item_main_4, R.id.fab})
    public void onClick(View view) {
        clearChecked();
        switch (view.getId()) {
            case R.id.tab_item_main_0:
                tabItemMain0.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_TODAY);
                break;

            case R.id.tab_item_main_1:
                tabItemMain1.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_INTEREST);
                break;

            case R.id.tab_item_main_2:
                tabItemMain2.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_SAFETY);
                break;

            case R.id.tab_item_main_3:
                tabItemMain3.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_SPORT);
                break;

            case R.id.tab_item_main_4:
                tabItemMain4.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_OTHER);
                break;

            case R.id.fab:
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show();
                break;
        }
    }

    private void clearChecked() {
        tabItemMain0.setChecked(false);
        tabItemMain1.setChecked(false);
        tabItemMain2.setChecked(false);
        tabItemMain3.setChecked(false);
        tabItemMain4.setChecked(false);
    }
}

avtivity里的内容还是比较少的,只是多了Fragment的显示和隐藏,没有变得复杂和职责不清,对于管理和维护也很简单。因为有多个view实现类,所以,我没有在presenter的初始化时传入view实例,而是通过在presenter中的setView方法设置不同的view。

跟retrofit2相关的内容这里不展开说,不知道的、想了解的可以自行搜索,这里是GitHub地址:retrofit,以及本文的项目源码:Login-MVP-Architecture ,希望对MVP感兴趣以及任何想要一起提高Android开发技能的小伙伴能star一下,以后会继续在这个项目上添加新的内容,包括一些有用的工具类、各种控件、效果实现、设计模式的使用等等。

周末愉快(●'◡'●)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容