MVP是個好東西,可是最近項目一直用的是mvc模式,先馬克下之前鼓搗過的mvp框架,過年回家再用它重構下代碼。
先上依賴庫####
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.android.support:design:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.2.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
建議寫死依賴的版本號,而不要使用“+”,避免版本升級帶了一些奇葩的問題。
依賴retrolambda####
在app.build依賴
apply plugin: 'me.tatarka.retrolambda'
再加上
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
然后在整個項目的build文件上面加入:
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
OK,到這里我們的環境搭建就完成了
首先我們看下項目的目錄結構:
BaseContract(基本的文件類,我們可以在里面寫上view層,model層,Presenter層的interface)
/**
* Created by Ly on 2016/11/2.
*/
public class BaseContract {
}
BaseModel(基本的model層,所有耗時操作應該寫在model層中)
/**
* Created by Ly on 2016/11/2.
*/
public interface BaseModel {
}
BaseView層(寫入跟用戶交互的方法集合類,比如showTosast,showDialog)
/**
* Created by Ly on 2016/11/2.
*/
public interface BaseView {
void TsShow(String msg);
}
看下我們的基本baseActivity.java:
package com.Ly.BaseJustTalk.base;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.MenuItem;
import android.widget.Toast;
import com.Ly.BaseJustTalk.R;
import butterknife.ButterKnife;
/**
* Created by Ly on 2017/1/12.
*/
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
private SwipeRefreshLayout mRefreshLayout;
private AppBarLayout mAppBar;
private Toolbar mToolbar;
private ProgressDialog mProgressBar;
protected Context mContext;
private boolean mIsRequestDataRefresh = false;
private static final String EXTRA_KEY = "extra";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dogetExtra();
mContext = this;
// 允許為空,不是所有的頁面都要實現這個模式
if (createPresenter() != null) {
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
setContentView(provideContentViewId());
ButterKnife.bind(this);
mAppBar = findViewById(R.id.app_bar_layout);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (mToolbar != null && mAppBar != null) {
setSupportActionBar(mToolbar); //把Toolbar當做ActionBar給設置
if (canBack()) {
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
//設置ActionBar一個返回箭頭,主界面沒有,次級界面有
actionBar.setDisplayHomeAsUpEnabled(true);
}
if (Build.VERSION.SDK_INT >= 21) {
//Z軸浮動
mAppBar.setElevation(10.6F);
}
}
}
if (isSetRefresh()) {
setupSwipeRefresh();
}
}
public static void doStartActivity(Context context, Bundle bundle, Class<?> clz) {
Intent intent = new Intent(context, clz);
if (bundle != null) {
intent.putExtra(EXTRA_KEY, bundle);
}
context.startActivity(intent);
}
protected abstract void dogetExtra();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 此時android.R.id.home即為返回箭頭
if (item.getItemId() == android.R.id.home) {
onBackPressed();
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
/**
* 生成下拉刷新
*/
private void setupSwipeRefresh() {
mRefreshLayout = findViewById(R.id.swipe_refresh);
if (null != mRefreshLayout) {
mRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
mRefreshLayout.setProgressViewOffset(true,
0,
(int) TypedValue.applyDimension
(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
.getDisplayMetrics()));
}
}
/**
* 設置刷新
*
* @param requestDataRefresh
*/
public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
}
/**
* 數據刷新
*/
public void requestDataRefresh() {
mIsRequestDataRefresh = true;
}
/**
* 判斷當前頁面是否可以返回,
* 主界面不可以返回 子界面可以放回
*
* @return
*/
public boolean canBack() {
return false;
}
/**
* 判斷子Activity是否需要上下拉刷新功能
*
* @return
*/
public Boolean isSetRefresh() {
return false;
}
/**
* 創建P
*
* @return
*/
protected abstract T createPresenter();
/**
* 引入布局文件
*
* @return
*/
protected abstract int provideContentViewId();
protected void ShowTs(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
protected void ShowDialog() {
ShowDialog(null);
}
protected void ShowDialog(String msg) {
if (TextUtils.isEmpty(msg)) {
msg = getString(R.string.loading);
}
mProgressBar = ProgressDialog.show(this, null, msg);
}
protected void DissDialog() {
if (mProgressBar != null && mProgressBar.isShowing()) {
mProgressBar.dismiss();
}
}
}
相對應的BaseFragment.java 代碼如下:
package com.Ly.BaseJustTalk.base;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.Ly.BaseJustTalk.R;
import butterknife.ButterKnife;
/**
* Created by Ly on 2011/1/12.
*/
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
protected Context mContext;
protected T mPresenter;
private boolean mIsRequestDataRefresh = false;
private SwipeRefreshLayout mRefreshLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(createViewLayoutId(), container, false);
ButterKnife.bind(this, rootView);
initView(rootView);
if (isSetRefresh()) {
setupSwipeRefresh(rootView);
}
return rootView;
}
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
private void setupSwipeRefresh(View view) {
mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh);
if (mRefreshLayout != null) {
mRefreshLayout.setColorSchemeResources(R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_3);
mRefreshLayout.setProgressViewOffset(true, 0, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mRefreshLayout.setOnRefreshListener(this::requestDataRefresh);
}
}
public void requestDataRefresh() {
mIsRequestDataRefresh = true;
}
public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
}
protected abstract T createPresenter();
protected abstract int createViewLayoutId();
protected void initView(View rootView) {
}
public Boolean isSetRefresh() {
return true;
}
}
Ok,直接看實例代碼來理解,就用登錄界面的吧。
其中LoginActiviy為用戶能看到的界面;
Contract是mvp的協議層;Model為數據處理層;Presenter可以理解為橋梁,負責溝通連接M層和V層。
在我的理解里的MVP是這個意思:我們可以看到,這個類持有了View和Model兩個模塊,在方法體里面,我們調用了model的方法去做耗時,在結果方法體里面我們調用了view的方法去修改UI,同時presenter這個模塊又被view持有了,view可以在聲明周期里面去調用特定的方法,view和presenter相互溝通,view和model完全隔離,presenter調控model,presenter溝通全局。
看下我們的登錄界面:
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.5"
android:gravity="center"
android:text="@string/text_welcome"
android:textSize="25sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="@dimen/margin_left"
android:layout_marginRight="@dimen/margin_right"
android:layout_weight="3"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/til_username"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_username"
style="@style/edit_style"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_username" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin6">
<EditText
android:id="@+id/et_password"
style="@style/edit_style"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_password" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/bt_login"
style="@style/button_style"
android:layout_width="match_parent"
android:layout_height="@dimen/button_height"
android:layout_marginTop="@dimen/margin20"
android:text="@string/text_login_reg" />
</LinearLayout>
</LinearLayout>
LoginContract.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import com.Ly.BaseJustTalk.base.BaseModel;
import com.Ly.BaseJustTalk.base.BaseView;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginContract {
interface LoginView extends BaseView {
String getUserName();
String getUserPass();
// userName錯誤后進行提示
void setUserNameErr(String errMsg);
// 密碼錯誤后進行提示
void setPassErr(String errMsg);
void doJustalkLogin();
void doLoginFial();
}
interface LoginModel extends BaseModel {
/**
* @param userName
* @param Pass
* @return 0 合法賬戶密碼 給予執行下一步
* -1 用戶名為空
* -2 用戶密碼為空
* -3 用戶名不合法
* -4 用戶密碼不合法
*/
int isRightUserNamePass(String userName, String Pass);
/**
* 進行justalk的登錄
*
* @param userName
* @param pass
* @return
*/
boolean doJustalkLogin(String userName, String pass);
}
interface LoginPresenter {
// 驗證用戶輸入的信息
void doVerificationInfo();
// 驗證正確后進行登錄
void doJustalkLogin();
}
}
LoginModel.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import android.text.TextUtils;
import android.util.Log;
import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.application.LyApplication;
import com.justalk.cloud.juslogin.LoginDelegate;
/**
* Created by Administrator on 2017/1/16.
*/
public class LoginModel implements LoginContract.LoginModel {
/**
* 驗證賬號合法性
*
* @param userName
* @param Pass
* @return 0 合法賬戶密碼 給予執行下一步
* -1 用戶名為空
* -2 用戶密碼為空
* -3 用戶名不合法
* -4 用戶密碼不合法
*/
@Override
public int isRightUserNamePass(String userName, String Pass) {
if (TextUtils.isEmpty(userName)) {
return -1;
} else if (TextUtils.isEmpty(Pass)) {
return -2;
} else if (userName.length() < 4 || userName.length() > 12) {
return -3;
} else if (Pass.length() < 6 || Pass.length() > 12) {
return -4;
} else
return 0;
}
@Override
public boolean doJustalkLogin( String userName, String pass) {
if (LoginDelegate.getInitState() == LoginDelegate.InitStat.MTC_INIT_FAIL) {
return false;
}
String server = LyApplication.getInstance().getString(R.string.JusTalkCloud_network_address);
String network = null;
if (!server.startsWith("sudp:")) {
network = server;
server = "sarc:arc@AccessEntry:99";
Log.e("testtest", server);
}
if (LoginDelegate.login(userName, pass, server, network)) {
return true;
}
return false;
}
}
LoginPresenter.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import com.Ly.BaseJustTalk.base.BasePresenter;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> implements
LoginContract.LoginPresenter {
private LoginContract.LoginView loginView;
private LoginContract.LoginModel loginModel;
public LoginPresenter(LoginContract.LoginView loginView) {
this.loginView = loginView;
loginModel = new LoginModel();
}
@Override
public void doVerificationInfo() {
int resultcode = this.loginModel.isRightUserNamePass(this.loginView.getUserName(), this.loginView.getUserPass());
switch (resultcode) {
case -1:
this.loginView.setUserNameErr("請輸入用戶名");
break;
case -2:
this.loginView.setPassErr("請輸入用戶密碼");
break;
case -3:
this.loginView.setUserNameErr("請檢查用戶名格式或長度");
break;
case -4:
this.loginView.setPassErr("請檢查密碼格式或長度");
break;
case 0:
this.loginView.setUserNameErr(null);
this.loginView.setPassErr(null);
this.loginView.doJustalkLogin();
break;
default:
this.loginView.TsShow("未知錯誤");
break;
}
}
@Override
public void doJustalkLogin() {
boolean isLoginSuccess = this.loginModel.doJustalkLogin(this.loginView.getUserName(), this.loginView.getUserPass());
if (!isLoginSuccess) {
this.loginView.doLoginFial();
}
}
}
LoginActivity.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.base.BaseActivity;
import com.Ly.BaseJustTalk.ui.activity.MainActivity;
import com.Ly.BaseJustTalk.utils.Signer;
import com.justalk.cloud.juslogin.LoginDelegate;
import com.justalk.cloud.lemon.MtcCli;
import com.justalk.cloud.lemon.MtcCliConstants;
import butterknife.Bind;
import butterknife.OnClick;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
implements LoginContract.LoginView, LoginDelegate.Callback {
@Bind(R.id.et_username)
EditText etUsername;
@Bind(R.id.til_username)
TextInputLayout tilUsername;
@Bind(R.id.et_password)
EditText etPassword;
@Bind(R.id.til_password)
TextInputLayout tilPassword;
@Bind(R.id.bt_login)
Button btLogin;
private LoginContract.LoginPresenter loginPresenter = new LoginPresenter(this);
@OnClick(R.id.bt_login)
void toLogin() {
loginPresenter.doVerificationInfo();
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter(this);
}
@Override
protected int provideContentViewId() {
return R.layout.activity_login;
}
@Override
public void TsShow(String msg) {
ShowTs(msg);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LoginDelegate.setCallback(this);
}
@Override
protected void dogetExtra() {
}
@Override
public String getUserName() {
return etUsername.getText().toString().trim();
}
@Override
public String getUserPass() {
return etPassword.getText().toString().trim();
}
@Override
public void setUserNameErr(String errMsg) {
tilUsername.setError(errMsg);
}
@Override
public void setPassErr(String errMsg) {
tilPassword.setError(errMsg);
}
@Override
public void doJustalkLogin() {
ShowDialog();
if (MtcCli.Mtc_CliGetState() != MtcCliConstants.EN_MTC_CLI_STATE_LOGINED) {
this.loginPresenter.doJustalkLogin();
} else {
MainActivity.doStartActivity(mContext, null, MainActivity.class);
}
}
@Override
public void doLoginFial() {
ShowTs(getString(R.string.tips_login_fail));
}
@Override
public void mtcLoginOk() {
MainActivity.doStartActivity(mContext, null, MainActivity.class);
}
@Override
public void mtcLoginDidFail() {
ShowTs(getString(R.string.tips_login_fail_justalk));
}
@Override
public void mtcLogoutOk() {
}
@Override
public void mtcLogouted() {
}
@Override
public void mtcAuthRequire(String s, String s1) {
Log.e("LHT", "mtcAuthRequire: " + s + "----" + s1);
String key =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"tRPBKBjUTEQwaKEjPy8YnX1bUODPB+7hto8KeGJbnCdCcnJdLxPFE7ld1skKIyPi" +
"YkyHj73JqA41ntHML2LNqw5Mhs1pewE4QLCu6icIUNtH8+bL53EhVnfdzwIDAQAB" +
"AoGAA6i6c5xHEGfKzoDHQJgiC5X9ZFAtES5AG3IMJmtF9flQyeoeDzRit+/FwNXi" +
"M1CKohnvLAJTvPs/8TBp5us4rabQ5Hnp+ylr7I2IbYIP2LV6TKkiTq/fBOJBxZiw" +
"qs0tjXxRZnC2IWqoCt/ciE4DXQIYVV3gYMRcKae5KZ3F2LECQQDqL4Sd2xyG0FsW" +
"cKwrlFRQ1cfFrSF/jmm2onkCZgDfq0R5aIGewpbTciLj8rf/Zq0XgAmCa3qQYo6M" +
"7G0OgIXTAkEAxbIC2xJocvPfEFbUd+hEWDFl/3LtQWZSHVLx9SXLXWSRY4/3dyRM" +
"6L78eQ2yWIVF4pxJrIHTbJqhxItlVM/elQJBAJ3jRZ0L8hKufQsHEf0btzD8wQB0" +
"doZCZOF+bumADgy+sp7MJ7/636dVZ1KZ/RWTixWx/DdS8UJRQFygtfI2EoMCQHky" +
"4tFPfb1LiStJMES6nnu6/R8YZB++DQVxPmjeXMjKyN9S+ZGPLZ9axwmnvfjK68c7" +
"rWcWyHlCa35FP0A5l+kCQB5cEu5Au1RkY9XfUodKmFhlCvdY8Ig0JgZ8DC6m+A31" +
"o4xrCoGHiPldKdCo0I7gQ4WMvoVNHCQyNv5qcw9t7uk=";
String code = Signer.signWithKey(key, s, s1, 3600);
LoginDelegate.promptAuthCode(code);
}
@Override
protected void onDestroy() {
super.onDestroy();
LoginDelegate.logout();
}
}
更新 這個框架里面的循環嵌套的方法
final String account = "111_111_1111", password = "1234556";
HttpRequest.getApiService().doRegister(account, password).concatMap(new Function<RegisterBean, Flowable<LoginUserBean>>() {
@Override
public Flowable<LoginUserBean> apply(RegisterBean registerBean) throws Exception {
if (registerBean.getStatus().equals(ErrCodeMessage.statusSuc)) {
XLog.e(registerBean.getMessage());
return HttpRequest.getApiService().doLogin(account, password);
} else {
return Flowable.empty();
}
}
}).compose(XApi.<LoginUserBean>getScheduler()).compose(XApi.<LoginUserBean>getApiTransformer())
.compose(this.<LoginUserBean>bindToLifecycle()).subscribe(new ApiSubscriber<LoginUserBean>() {
@Override
protected void onFail(NetError error) {
XLog.e("xxxxxxxxxxxx" + error.getMessage());
}
@Override
public void onNext(LoginUserBean loginUserBean) {
XLog.e("xxxxxxxxxxxx" + loginUserBean.toString());
}
});
如果注冊失敗的時候 返回 return Flowable.empty(); 一個空的