MVP+Nucleus

今天给大家带来的是一个mvp框架,nucleus。这个框架是由国外的一位大神konmik搭建的,对mvp进行了一个封装,那么就先给大家说说MVP模式。

MVP模式

MVP是从经典的模式MVC演变而来,不同的是MVP中Model层并不会直接与View层有任何关系,而是通过Presenter来进行交互,至于MVP的好处这里我也不多陈述,网上有很多文章都对MVP的优点进行介绍,下面就先以传统的MVP给大家写一个登录的Demo感受一下。

 

MVP结构

传统MVP登录

这里是以Contract模式来分包,使用contract的好处是因为我们有一个插件,叫MVPHelper,一键生成Presenter和Model层的代码。

LoginContract

LoginActivity

首先用LoginActivity实现LoginContract.View并重写里面的方法,然后在onCreate中拿到对应Presenter层的引用,大家可以看到这里没有任何的逻辑判断,只有界面相关的操作

package cn.lxt.mvpdemo.view.activity;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import cn.lxt.mvpdemo.R;
import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.presenter.LoginPresenter;

//首先要实现LoginContract.View
public class LoginActivity extends AppCompatActivity implements LoginContract.View, View.OnClickListener {

    private ProgressDialog mProgressDialog;
    private EditText mEtName;
    private EditText mEtPsw;
    private LoginContract.Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //拿到Presenter层的引用
        mPresenter = new LoginPresenter(this);
        mEtName = (EditText) findViewById(R.id.et_name);
        mEtPsw = (EditText) findViewById(R.id.et_psw);
        Button button = (Button) findViewById(R.id.btn_login);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:
                //点击登录
                String name = mEtName.getText().toString().trim();
                String psw = mEtPsw.getText().toString().trim();
                mPresenter.login(name, psw);
                break;
        }
    }

    //登陆成功后会走这里
    @Override
    public void loginSuccess() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
    }

     //登陆失败后会走这里
    @Override
    public void loginFailed(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showDialog() {
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setMessage("登陆中");
        mProgressDialog.show();
    }

    @Override
    public void hideDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing())
            mProgressDialog.dismiss();
    }
}

LoginPresenter

用MvpHelper这个插件一键生成该类,同样的重写方法,在构造中拿到对应Model层的引用。

package cn.lxt.mvpdemo.presenter;

import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.model.LoginModel;
import cn.lxt.mvpdemo.view.activity.LoginActivity;

/**
 * Created by Administrator on 2017/8/17 0017.
 */
//这个类是自动生成的
public class LoginPresenter implements LoginContract.Presenter {
    private final LoginActivity loginActivity;
    private final LoginContract.Model mModel;

    public LoginPresenter(LoginActivity loginActivity) {
        this.loginActivity = loginActivity;
        mModel = new LoginModel(this);
    }

    @Override
    public void login(String name, String psw) {
        //通知view层显示dialog
        loginActivity.showDialog();
        //通知model层调用登录
        mModel.login(name, psw);
    }

    @Override
    public void loginSuccess() {
        //登陆成功之后的回调
        loginActivity.hideDialog();
        loginActivity.loginSuccess();        
    }

    @Override
    public void loginFailed(String msg) {
        //登陆失败之后的回调
        loginActivity.hideDialog();
        loginActivity.loginFailed(msg);
    }
}

LoginModel

这个类也是插件一键生成的,同样的,在构造中拿到Presenter的引用,在model层处理业务逻辑。

package cn.lxt.mvpdemo.model;

import android.text.TextUtils;

import java.util.concurrent.TimeUnit;

import cn.lxt.mvpdemo.contract.LoginContract;
import cn.lxt.mvpdemo.presenter.LoginPresenter;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

/**
 * Created by Administrator on 2017/8/17 0017.
 */

public class LoginModel implements LoginContract.Model {
    private final LoginPresenter loginPresenter;

    public LoginModel(LoginPresenter loginPresenter) {
        this.loginPresenter = loginPresenter;
    }

    @Override
    public void login(final String name, final String psw) {
        //这里我们做逻辑处理
        if (TextUtils.isEmpty(name)) {
            loginPresenter.loginFailed("姓名不允许为空");
        } else if (TextUtils.isEmpty(psw)) {
            loginPresenter.loginFailed("密码不允许为空");
        } else {
            //这里模拟登录过程
            Observable.timer(2, TimeUnit.SECONDS)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<Long>() {
                        @Override
                        public void accept(@NonNull Long aLong) throws Exception {
                            loginPresenter.loginSuccess();
                        }
                    });
        }
    }
}

效果很明显,view层没有任何的逻辑处理,有的只是页面的显示,presenter层只是负责view层和model层的交互,model层只是负责逻辑的处理,分工明确,当然,这只是一个最基础的MVP模式,还有很多情况没有考虑到,那么接下来给大家带来一套成熟的MVP框架,也就上面说的nucleus。

Nucleus

Nucleus 是一个实现MVP+Rxjava的框架,首先给大家说一下他的好处

  1. 它支持在View/Fragment/Activity的Bundle中保存/恢复Presenter的状态,一个Presenter可以保存它的请求参数到bundles中,以便之后重启它们
  2. 它允许一个View实例持有多个Presenter对象
  3. 快速实现View和Presenter的绑定
  4. 提供线程的基类以便复用
  5. 支持在进程重启后,自动重新发起请求,在onDestroy方法中,自动退订RxJava订阅
  6. 使用相当简单重启后台任务的方式

 

Necleus提供了3种恢复后台任务的方式,restartableFirst,restartableLatestCache,restartableReplay. 面我们看一下它们之间“从Presenter将后台任务结果发送到View”的区别

restartableFirst只会提交第一个数据

restartableLatestCache只会提交最新的数据,而且会将它缓存起来,每当View重新Attach到Presenter的时候都会重新提交

restartableReplay提交所有的数据,每当View重新Attach到Presenter的时候都会重新提交

nucleus使用(基于rxjava,不会rxjava的小伙伴请看我之前的文章)

第一步,添加依赖

    compile 'info.android15.nucleus5:nucleus:5.0.0-beta1'
    compile 'info.android15.nucleus5:nucleus-support-v4:5.0.0-beta1'
    compile 'info.android15.nucleus5:nucleus-support-v7:5.0.0-beta1'

    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'

第二步,继承

用你项目中的BaseActivity去继承他的NucleusAppCompatActivity,比如

这样你所有继承BaseActivity的Activity就都可以使用了。

用你项目中的BaseFragment去继承他的NucleusSupportFragment

用你的BasePresenter继承他的RxPresenter

 

到此为止准备工作已经做完了。

第三步,网络请求

还是以刚才的登录为例子,不同的是这里用到了Retrofit+RxJava的模式做的联网请求,我这里用的是真实的网络请求,不是模拟的了。

首先,在LoginActivity中增加一个注解,里面的参数传入对应的Presenter类,直接上代码

package cn.lxt.nucleusdemo.view.activity;

import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import cn.lxt.nucleusdemo.R;
import cn.lxt.nucleusdemo.base.BaseActivity;
import cn.lxt.nucleusdemo.presenter.LoginPresenter;
import nucleus5.factory.RequiresPresenter;

//添加一个注解,参数为这个类所对应的Presenter
@RequiresPresenter(LoginPresenter.class)
public class LoginActivity extends BaseActivity<LoginPresenter> implements View.OnClickListener {

    private EditText mEtName;
    private EditText mEtPsw;
    private Button mButton;

    @Override
    protected void initLayout() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void initView() {
        mEtName = (EditText) findViewById(R.id.et_name);
        mEtPsw = (EditText) findViewById(R.id.et_psw);
        mButton = (Button) findViewById(R.id.btn_login);
    }

    @Override
    protected void initData() {
    }

    @Override
    protected void initClickListener() {
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:
                String name = mEtName.getText().toString().trim();
                String psw = mEtPsw.getText().toString().trim();
                //注意,当你点击登录时,可以调用getPresenter()拿到对应P层的引用
                getPresenter().login(this, name, psw);
                break;
        }
    }

    public void loginSuccess() {
        Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
    }

    public void loginFailed() {
        Toast.makeText(this, "登陆失败", Toast.LENGTH_SHORT).show();
    }
}

重头戏来了,Presenter里面的逻辑代码

还是同样的,继承你自己的BasePresenter,不同的是你可以使用nucleus帮你封装的生命周期方法了,重写onCreate方法。
在你需要调用请求的地方,调用start()方法,参数为一个int值,你可以自己定义。

package cn.lxt.nucleusdemo.presenter;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;

import cn.lxt.nucleusdemo.api.Service;
import cn.lxt.nucleusdemo.base.BasePresenter;
import cn.lxt.nucleusdemo.response.LoginResponse;
import cn.lxt.nucleusdemo.retrofit.RetrofitUtil;
import cn.lxt.nucleusdemo.view.activity.LoginActivity;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.BiConsumer;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import nucleus5.presenter.Factory;

/**
 * Created by Administrator on 2017/8/17 0017.
 */

public class LoginPresenter extends BasePresenter<LoginActivity> {

    private String name, psw;
    private final int REQUSET_LOGIN = 0;

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        //调用该方法开始一个请求,第一个参数就是你start里面传入的int值,第二个参数就是一个Factory,所有的联网逻辑都写在里面,这里结合了Retrofit,第三个参数就是请求成功的回调,第四个参数就是请求失败的回调
        restartableLatestCache(REQUSET_LOGIN, new Factory<Observable<LoginResponse>>() {
            @Override
            public Observable<LoginResponse> create() {
                return RetrofitUtil.getRetrofit(context)
                        .create(Service.class)
                        .login(name, psw, "APP")
                        .subscribeOn(Schedulers.io())
                        .doOnSubscribe(new Consumer<Disposable>() {
                            @Override
                            public void accept(@NonNull Disposable disposable) throws Exception {
                                ((LoginActivity) context).showDialog();
                            }
                        })
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new BiConsumer<LoginActivity, LoginResponse>() {
            @Override
            public void accept(LoginActivity loginActivity, LoginResponse loginResponse) throws Exception {
                loginActivity.loginSuccess();
                loginActivity.hideDialog();
                //请求成功之后调用stop(),参数为start里面传入的参数
                stop(REQUSET_LOGIN);
            }
        }, new BiConsumer<LoginActivity, Throwable>() {
            @Override
            public void accept(LoginActivity loginActivity, Throwable throwable) throws Exception {
                loginActivity.loginFailed();
                loginActivity.hideDialog();
                //请求失败之后调用stop(),参数为start里面传入的参数
                stop(REQUSET_LOGIN);
            }
        });
    }

    public void login(Context context, String name, String psw) {
        if (TextUtils.isEmpty(name)) {
            Toast.makeText(context, "用户名不能为空", Toast.LENGTH_SHORT).show();
        } else if (TextUtils.isEmpty(psw)) {
            Toast.makeText(context, "密码不能为空", Toast.LENGTH_SHORT).show();
        } else {
            this.context = context;
            this.name = name;
            this.psw = psw;
            start(REQUSET_LOGIN);
        }
    }
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值