java二方包封装,带你封装自己的MVP+Retrofit+RxJava2框架(二)

前言

本篇文章是针对上一篇文章:带你封装自己的MVP+Retrofit+RxJava2框架(一)的进一步封装改进,建议在看完上一篇文章后,再食用本文效果更佳!

本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:我的GIthub博客

本篇文章需要已经具备的知识:

MVP的概念和基本使用

Retrofit框架的基本使用

RxJava2框架的基本使用

ButterKnife框架的基本使用

Base基类的概念

学习清单:

Base实体类的封装

Base异常类的封装

Base观察者的封装

RxJava线程自动调度的小技巧

进行网络请求自动显示加载中

完成网络请求自动关闭加载中

自动处理异常信息

Cookie自动持久化与Retrofit的协同使用

接口管理Retrofit请求接口的优美方式

一.为什么要封装这套框架

​ 如上一篇文章所说,在MVP模式日渐流行的时候,封装一套MVP框架,不仅对日常的开发大大便利,还能提前积累一下未来在实际工作中的技巧,并且,良好的封装和规范使用还能减少开发中的各种令人头疼的BUG。

​ 有人可能会问:“你上一篇不是也写了MVP框架吗?你这篇难道还是一样的吗?难道你是换汤不换药吗?”

​ 其实,一开始笔者自以为我上一篇文章封装的MVP框架已经够不错了,但是,在笔者某天看了yechaoa大神玩安卓java的源码后,被其封装的MVP框架的所折服,因此第一时间写这篇文章,想向大家分享下,笔者从中汲取的经验,希望能够帮助到各位!

精简了Activity基类,将原来的两个BaseActivity和BaseMvpActivity精简为一个BaseActivity

修复了当继承了Activity基类,不添加Presenter会导致空指针的Bug

添加了网络请求可选择自动显示加载中和自动关闭加载中的功能

添加了自动处理异常信息的功能

封装了一个Bean对象的基类

精简了RxJava的用法,因此可以省去Model类的编写

封装了一个Observer的基类

增添了cookie自动持久化的功能

改进了RetrofitService的封装,将Retrofit接口的实例化引入基类

二.核心用法与样例分析

本项目基于Android X 进行构建,完整代码已经上传到我的Github仓库

首先,先给大家介绍下笔者项目的基本结构

66201ae31114

项目基本结构

为了给大家模拟带自动获取Cookie的功能,所以笔者设计了一个具有登陆,注册,收藏功能的Demo

在这里特别感谢玩安卓提供的API

66201ae31114

Demo截图

笔者在Demo中用到的框架如下

implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'androidx.appcompat:appcompat:1.1.0'

implementation 'androidx.legacy:legacy-support-v4:1.0.0'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

testImplementation 'junit:junit:4.12'

androidTestImplementation 'androidx.test.ext:junit:1.1.1'

androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

implementation 'com.google.android.material:material:1.1.0'

//cardView

implementation 'androidx.cardview:cardview:1.0.0'

/*retrofit、rxjava*/

implementation 'com.squareup.retrofit2:retrofit:2.6.2'

implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'

implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'

/*glide*/

implementation 'com.github.bumptech.glide:glide:4.10.0'

annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'

/*butterknife*/

implementation 'com.jakewharton:butterknife:10.2.0'

annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'

/*YUtils*/

implementation 'com.github.yechaoa:YUtils:2.1.0'

/*BRVAH*/

implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'

/*banner*/

implementation 'com.youth.banner:banner:1.4.10'

下面笔者将为大家详细介绍每个类的相关信息

2.1 Base基类

2.1.1 BaseActivity

BaseActivity相对于笔者上一个版本的MVP框架的改进之处:

将两个基类Activity合并为一个BaseActivity

在其中封装了进度条的显示和隐藏的方法

/**

* Description : BaseActivity

*

* @author XuCanyou666

* @date 2020/2/7

*/

public abstract class BaseActivity

extends AppCompatActivity implements BaseView {

protected P presenter;

protected abstract P createPresenter();

protected abstract int getLayoutId();

protected abstract void initView();

protected abstract void initData();

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//设置竖屏

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

setContentView(LayoutInflater.from(this).inflate(getLayoutId(), null));

ButterKnife.bind(this);

presenter = createPresenter();

initView();

initData();

}

@Override

protected void onResume() {

super.onResume();

initListener();

}

@Override

protected void onDestroy() {

super.onDestroy();

//销毁时,解除绑定

if (presenter != null) {

presenter.detachView();

}

}

protected void initListener() {

}

@Override

public void showLoading() {

YUtils.showLoading(this, "加载中");

}

@Override

public void hideLoading() {

YUtils.dismissLoading();

}

/**

* 可以处理异常

*/

@Override

public void onErrorCode(BaseBean bean) {

}

/**

* 启动activity

*

* @param activity 当前活动

* @param isFinish 是否结束当前活动

*/

public void startActivity(Class> activity, boolean isFinish) {

Intent intent = new Intent(this, activity);

startActivity(intent);

if (isFinish) {

finish();

}

}

}

2.1.2 BaseFragment

/**

* Description : BaseFragment

*

* @author XuCanyou666

* @date 2020/2/7

*/

public abstract class BaseFragment

extends Fragment implements BaseView {

private Unbinder unbinder;

protected Context mContext;

protected P presenter;

protected abstract P createPresenter();

protected abstract int getLayoutId();

protected abstract void initView();

protected abstract void initData();

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View view = inflater.inflate(getLayoutId(), container, false);

unbinder = ButterKnife.bind(this, view);

//得到context,在后面的子类Fragment中都可以直接调用

mContext = ActivityUtil.getCurrentActivity();

presenter = createPresenter();

initView();

initData();

return view;

}

@Override

public void onResume() {

super.onResume();

initListener();

}

@Override

public void onDestroyView() {

super.onDestroyView();

//do something

unbinder.unbind();

//销毁时,解除绑定

if (presenter != null) {

presenter.detachView();

}

}

private void initListener() {

}

@Override

public void onErrorCode(BaseBean bean) {

}

/**

* 显示加载中

*/

@Override

public void showLoading() {

YUtils.showLoading(ActivityUtil.getCurrentActivity(), "加载中");

}

/**

* 隐藏加载中

*/

@Override

public void hideLoading() {

YUtils.dismissLoading();

}

}

2.1.3 BasePresenter

BasePresenter相对于笔者上一个版本的MVP框架的改进之处:

将线程的调度写入了addDisposable中

改写了addDisposable方法,使得调用方式更加简单优美

/**

* Description : BasePresenter

*

* @author XuCanyou666

* @date 2020/2/7

*/

public class BasePresenter {

private CompositeDisposable compositeDisposable;

public V baseView;

/**

* 这个后面可以直接用 Example:apiServer.login(username, password);

*/

protected API.WAZApi apiServer = RetrofitService.getInstance().getApiService();

public BasePresenter(V baseView) {

this.baseView = baseView;

}

/**

* 解除绑定

*/

public void detachView() {

baseView = null;

removeDisposable();

}

/**

* 返回 view

*/

public V getBaseView() {

return baseView;

}

public void addDisposable(Observable> observable, BaseObserver observer) {

if (compositeDisposable == null) {

compositeDisposable = new CompositeDisposable();

}

compositeDisposable

.add(observable.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribeWith(observer));

}

private void removeDisposable() {

if (compositeDisposable != null) {

compositeDisposable.dispose();

}

}

}

2.1.4 BaseObserver

Observer的基类,提供了自动显示和自动隐藏进度条的方法

对内处理了onStart,onError,onComplete方法

对外只提供了onSuccess和onError方法,符合用户一般使用习惯

/**

* Description : BaseObserver

*

* @author XuCanyou666

* @date 2020/2/7

*/

public abstract class BaseObserver extends DisposableObserver {

protected BaseView view;

private boolean isShowDialog;

protected BaseObserver(BaseView view) {

this.view = view;

}

/**

* 带进度条的初始化方法

*

* @param view view

* @param isShowDialog 是否显示进度条

*/

protected BaseObserver(BaseView view, boolean isShowDialog) {

this.view = view;

this.isShowDialog = isShowDialog;

}

@Override

protected void onStart() {

if (view != null && isShowDialog) {

view.showLoading();

}

}

@Override

public void onNext(T o) {

onSuccess(o);

}

@Override

public void onError(Throwable e) {

if (view != null && isShowDialog) {

view.hideLoading();

}

BaseException be;

if (e != null) {

//自定义异常

if (e instanceof BaseException) {

be = (BaseException) e;

//回调到view层 处理 或者根据项目情况处理

if (view != null) {

// 处理登录失效 更新

view.onErrorCode(new BaseBean(be.getErrorCode(), be.getErrorMsg()));

} else {

onError(be.getErrorMsg());

}

//系统异常

} else {

if (e instanceof HttpException) {

//HTTP错误

be = new BaseException(BaseException.BAD_NETWORK_MSG, e);

} else if (e instanceof ConnectException || e instanceof UnknownHostException) {

//连接错误

be = new BaseException(BaseException.CONNECT_ERROR_MSG, e);

} else if (e instanceof InterruptedIOException) {

//连接超时

be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e);

} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {

//解析错误

be = new BaseException(BaseException.PARSE_ERROR_MSG, e);

} else {

be = new BaseException(BaseException.OTHER_MSG, e);

}

}

} else {

be = new BaseException(BaseException.OTHER_MSG);

}

onError(be.getErrorMsg());

}

@Override

public void onComplete() {

if (view != null && isShowDialog) {

view.hideLoading();

}

}

public abstract void onSuccess(T o);

public abstract void onError(String msg);

}

2.1.5 BaseException

异常的基类

/**

* Description : BaseException

*

* @author XuCanyou666

* @date 2020/2/7

*/

public class BaseException extends IOException {

/**

* 解析数据失败

*/

public static final String PARSE_ERROR_MSG = "解析数据失败";

/**

* 网络问题

*/

public static final String BAD_NETWORK_MSG = "网络问题";

/**

* 连接错误

*/

public static final String CONNECT_ERROR_MSG = "连接错误";

/**

* 连接超时

*/

public static final String CONNECT_TIMEOUT_MSG = "连接超时";

/**

* 未知错误

*/

public static final String OTHER_MSG = "未知错误";

private String errorMsg;

private int errorCode;

public String getErrorMsg() {

return errorMsg;

}

public int getErrorCode() {

return errorCode;

}

public BaseException(String message) {

this.errorMsg = message;

}

public BaseException(String errorMsg, Throwable cause) {

super(errorMsg, cause);

this.errorMsg = errorMsg;

}

public BaseException(int errorCode, String message) {

this.errorMsg = message;

this.errorCode = errorCode;

}

}

2.1.6 BaseBean

实体类的基类,方便处理返回的Json数据,具体的写法需根据每个API而定

/**

* Description : BaseBean 实体类的基类

*

* @author XuCanyou666

* @date 2020/2/7

*/

public class BaseBean implements Serializable {

/**

* data :

* errorCode : 0

* errorMsg :

*/

public int errorCode;

public String errorMsg;

public T data;

public BaseBean(int code, String data) {

this.errorCode = code;

this.data = (T) data;

}

}

2.1.7 BaseView

/**

* Description : BaseView

*

* @author XuCanyou666

* @date 2020/2/7

*/

public interface BaseView {

void showLoading();

void hideLoading();

void onErrorCode(BaseBean bean);

}

2.2 http

2.2.1 cookie

持久化cookie,因为代码太多,这里只展示一个类的代码,详细代码请前往我的Github查看

package com.users.xucanyou666.rxjava2_retrofit_mvp2.http.cookie;

import android.content.Context;

import java.util.List;

import okhttp3.Cookie;

import okhttp3.CookieJar;

import okhttp3.HttpUrl;

/**

* Created by yechao on 2019/11/19/019.

* Describe :

*/

public class CookiesManager implements CookieJar {

private final PersistentCookieStore cookieStore;

public CookiesManager(Context context) {

cookieStore = new PersistentCookieStore(context);

}

@Override

public void saveFromResponse(HttpUrl url, List cookies) {

if (cookies.size() > 0) {

for (Cookie item : cookies) {

cookieStore.add(url, item);

}

}

}

@Override

public List loadForRequest(HttpUrl url) {

return cookieStore.get(url);

}

}

2.2.2 gson

重写ResponseBodyConverter对Json预处理,这里只展示一个类的代码,详细代码请前往我的Github

/**

* Created by yechao on 2019/11/18/018.

* Describe : 重写ResponseBodyConverter对json预处理

*/

public class BaseResponseBodyConverter implements Converter {

private final TypeAdapter adapter;

/**

* 登陆失效

*/

private static final int LOG_OUT_TIME = -1001;

BaseResponseBodyConverter( TypeAdapter adapter) {

this.adapter = adapter;

}

@Override

public T convert(ResponseBody value) throws IOException {

String jsonString = value.string();

try {

JSONObject object = new JSONObject(jsonString);

int code = object.getInt("errorCode");

if (0 != code) {

String data;

//错误信息

if (code == LOG_OUT_TIME) {

data = "登录失效,请重新登录";

} else {

data = object.getString("errorMsg");

}

//异常处理

throw new BaseException(code, data);

}

//正确返回整个json

return adapter.fromJson(jsonString);

} catch (JSONException e) {

e.printStackTrace();

//数据解析异常即json格式有变动

throw new BaseException(BaseException.PARSE_ERROR_MSG);

} finally {

value.close();

}

}

}

2.2.3 API

原因:随着项目日渐庞大,请求也越来越多,不可能每个请求都使用一个接口,否则不但造成浪费,而且不方便管理

作用:新建一个API作为Retrofit的管理类,用一个接口管理所有网络请求,可以有效改善代码质量

/**

* Description : API

* 接口的管理类

*

* @author XuCanyou666

* @date 2020/2/7

*/

public class API {

static final String BASE_URL = "https://www.wanandroid.com/";

public interface WAZApi {

//-----------------------【首页相关】----------------------

//首页文章列表 这里的{}是填入页数

@GET("article/list/{page}/json")

Observable> getArticleList(@Path("page") Integer page);

//-----------------------【登录注册】----------------------

//登录

@FormUrlEncoded

@POST("user/login")

Observable> login(@Field("username") String username, @Field("password") String password);

//注册

@FormUrlEncoded

@POST("user/register")

Observable> register(@Field("username") String username, @Field("password") String password, @Field("repassword") String repassword);

//-----------------------【 收藏 】----------------------

//收藏站内文章

@POST("lg/collect/{id}/json")

Observable collectIn(@Path("id") Integer id);

//取消收藏---文章列表

@POST("lg/uncollect_originId/{id}/json")

Observable uncollect(@Path("id") Integer id);

}

}

2.2.4 RetrofitService

Retrofit的配置类,在里面初始化了apiServer对象,并配置了日志信息,超时时间,Cookie持久化,用了静态内部类的单例模式

/**

* Description : RetrofitService

*

* @author XuCanyou666

* @date 2020/2/8

*/

public class RetrofitService {

private volatile static RetrofitService apiRetrofit;

private API.WAZApi apiServer;

/**

* 单例调用

*

* @return RetrofitService

*/

public static RetrofitService getInstance() {

if (apiRetrofit == null) {

synchronized (Object.class) {

if (apiRetrofit == null) {

apiRetrofit = new RetrofitService();

}

}

}

return apiRetrofit;

}

/**

* 获取api对象

*

* @return api对象

*/

public API.WAZApi getApiService() {

return apiServer;

}

/**

* 初始化retrofit

*/

private RetrofitService() {

//配置okHttp并设置时间、日志信息和cookies

HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();

httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient = new OkHttpClient.Builder()

.addInterceptor(httpLoggingInterceptor)

//设置超时时间

.connectTimeout(15, TimeUnit.SECONDS)

//设置Cookie持久化

.cookieJar(new CookiesManager(XUtil.getApplication()))

.build();

//关联okHttp并加上rxJava和Gson的配置和baseUrl

Retrofit retrofit = new Retrofit.Builder()

.client(okHttpClient)

.addConverterFactory(ScalarsConverterFactory.create())

.addConverterFactory(BaseConverterFactory.create())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.baseUrl(API.BASE_URL)

.build();

apiServer = retrofit.create(API.WAZApi.class);

}

}

2.3 bean

这里的带有嵌套的实体类看似很复杂,其实可以通过AS的GsonFormat插件一键生成

注意:不要将data,errorCode,errorMsg的导入到实体类中

2.3.1 Article

文章内容的实体类

/**

* Description : Article

*

* @author XuCanyou666

* @date 2020/2/8

*/

public class Article {

/**

* curPage : 2

* datas : [{"apkLink":"","author":"叶应是叶","chapterId":67,"chapterName":"网络基......."}]

* offset : 20

* over : false

* pageCount : 62

* size : 20

* total : 1224

*/

public int curPage;

public int offset;

public boolean over;

public int pageCount;

public int size;

public int total;

public List datas;

public static class DataDetailBean {

/**

* apkLink :

* author : 叶应是叶

* chapterId : 67

* chapterName : 网络基础

* collect : false

* courseId : 13

* desc :

* envelopePic :

* fresh : false

* id : 2809

* link : https://www.jianshu.com/p/6d2f324c8f42

* niceDate : 2018-04-12

* origin :

* projectLink :

* publishTime : 1523532264000

* superChapterId : 98

* superChapterName : 网络访问

* tags : []

* title : 在 Android 设备上搭建 Web 服务器

* type : 0

* visible : 1

* zan : 0

*/

public String apkLink;

public String author;

public int chapterId;

public String chapterName;

public boolean collect;

public int courseId;

public String desc;

public String envelopePic;

public boolean fresh;

public int id;

public int originId;

public String link;

public String niceDate;

public String origin;

public String projectLink;

public long publishTime;

public int superChapterId;

public String superChapterName;

public String title;

public int type;

public int visible;

public int zan;

public List> tags;

}

}

2.3.2 User

/**

* GitHub : https://github.com/yechaoa

* CSDN : http://blog.csdn.net/yechaoa

*

* Created by yechao on 2018/5/2.

* Describe :

*/

public class User {

/**

* collectIds : []

* email :

* icon :

* id : 3

* password : 111111

* type : 0

* username : 111111

*/

public String email;

public String icon;

public int id;

public String password;

public int type;

public String username;

public List> collectIds;

public String repassword;

}

2.4 module

这里分模块化进行管理,本Demo有Login,Register,Home总共三个模块

限于篇幅,在这里仅说明一个模块,其他模块的写法类似,具体写法,可以上Github查看

2.4.1 login

2.4.1.1 ILoginView

LoginView层的接口

/**

* Description : ILoginView

*

* @author XuCanyou666

* @date 2020/2/8

*/

public interface ILoginView extends BaseView {

/**

* 显示登陆成功

*

* @param successMessage 成功信息

*/

void showLoginSuccess(String successMessage);

/**

* 显示登陆失败

*

* @param errorMessage 失败信息

*/

void showLoginFailed(String errorMessage);

void doSuccess(BaseBean user);

}

2.4.1.2 LoginPresenter

这里因为RxJava经过封装后,Model层的代码比较精简,所以将Model直接写入Presenter中,以节省工作量

/**

* Description : LoginPresenter

*

* @author XuCanyou666

* @date 2020/2/8

*/

class LoginPresenter extends BasePresenter {

LoginPresenter(ILoginView baseView) {

super(baseView);

}

/**

* 登陆

*

* @param username username

* @param password password

* @param usernameCountMax 账号规定输入字符最大值

* @param passwordCountMax 密码规定输入字符最大值

*/

void login(String username, String password, int usernameCountMax, int passwordCountMax) {

YUtils.closeSoftKeyboard();

//判断输入的账号密码是否符合规范

if (isValid(username, password, usernameCountMax, passwordCountMax)) {

addDisposable(apiServer.login(username, password), new BaseObserver>(baseView, true) {

@Override

public void onSuccess(BaseBean bean) {

baseView.showLoginSuccess("登录成功( ̄▽ ̄)");

//将登陆的账号存进sp里面

SpUtil.setBoolean(GlobalConstant.IS_LOGIN, true);

SpUtil.setString(GlobalConstant.USERNAME, bean.data.username);

SpUtil.setString(GlobalConstant.PASSWORD, bean.data.password);

baseView.doSuccess();

}

@Override

public void onError(String msg) {

baseView.showLoginFailed(msg + "(°∀°)ノ");

}

});

} else {

baseView.showLoginFailed("填写错误 (°∀°)ノ");

}

}

/**

* 判断输入的账号密码是否符合规范

*

* @param userName username

* @param password password

* @param usernameCountMax 账号规定输入字符最大值

* @param passwordCountMax 密码规定输入字符最大值

* @return 是否合规

*/

private boolean isValid(String userName, String password, int usernameCountMax, int passwordCountMax) {

return check(userName, usernameCountMax) && check(password, passwordCountMax);

}

/**

* 判断输入是否规范

*

* @param string 输入的内容

* @param tilCounterMaxLength textInputLayout控件的输入字符的最大长度

* @return 是否合规

*/

private boolean check(String string, int tilCounterMaxLength) {

return !TextUtils.isEmpty(string) && string.length() <= tilCounterMaxLength && tilCounterMaxLength / 2 <= string.length();

}

}

2.4.1.3 LoginTextWatcher

登陆界面输入框的监听器

/**

* TextInputLayout监听器

* created by xucanyou666

* on 2020/2/7 18:09

* email:913710642@qq.com

*/

public class LoginTextWatcher implements android.text.TextWatcher {

private TextInputLayout mTilUsername;

private TextInputLayout mTilPassword;

LoginTextWatcher(TextInputLayout username, TextInputLayout password) {

mTilUsername = username;

mTilPassword = password;

}

@Override

public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

}

@Override

public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

}

@Override

public void afterTextChanged(Editable s) {

checkInput(mTilUsername);

checkInput(mTilPassword);

}

/**

* 判断输入内容是否合法

*

* @param textInputLayout textInputLayout

*/

public static void checkInput(TextInputLayout textInputLayout) {

if (textInputLayout != null) {

if (textInputLayout.getEditText().getText().length() > textInputLayout.getCounterMaxLength()) {

textInputLayout.setError("输入内容超过上限");

} else if (textInputLayout.getEditText().getText().length() < textInputLayout.getCounterMaxLength() / 2) {

textInputLayout.setError("最少6位");

} else {

textInputLayout.setError(null);

}

}

}

}

2.4.1.4 LoginActivity

/**

* Description : LoginActivity

*

* @author XuCanyou666

* @date 2020/2/8

*/

public class LoginActivity extends BaseActivity implements ILoginView {

@BindView(R.id.et_username)

EditText mEtUsername;

@BindView(R.id.til_username)

TextInputLayout mTilUsername;

@BindView(R.id.et_password)

EditText mEtPassword;

@BindView(R.id.til_password)

TextInputLayout mTilPassword;

@BindView(R.id.btn_login)

Button mBtnLogin;

@BindView(R.id.btn_register)

Button mBtnRegister;

@Override

protected LoginPresenter createPresenter() {

return new LoginPresenter(this);

}

@Override

protected int getLayoutId() {

return R.layout.activity_login;

}

@Override

protected void initData() {

}

@Override

protected void initView() {

LoginTextWatcher textWatcher = new LoginTextWatcher(mTilUsername, mTilPassword);

mEtUsername.addTextChangedListener(textWatcher);

mEtPassword.addTextChangedListener(textWatcher);

}

@Override

public void showLoginSuccess(String successMessage) {

ToastUtil.showToast(successMessage);

}

@Override

public void showLoginFailed(String errorMessage) {

ToastUtil.showToast(errorMessage);

}

@Override

public void doSuccess() {

startActivity(MainActivity.class, true);

}

@OnClick({R.id.btn_login, R.id.btn_register})

public void onViewClicked(View view) {

switch (view.getId()) {

case R.id.btn_login:

String username = mEtUsername.getText().toString().trim();

String password = mEtPassword.getText().toString().trim();

int tilUsernameCounterMaxLength = mTilUsername.getCounterMaxLength();

int tilPasswordCounterMaxLength = mTilPassword.getCounterMaxLength();

presenter.login(username, password, tilUsernameCounterMaxLength, tilPasswordCounterMaxLength);

break;

case R.id.btn_register:

YUtils.closeSoftKeyboard();

startActivity(RegisterActivity.class, false);

break;

default:

break;

}

}

}

三.我在使用中遇到的问题

3.1 颜色的资源文件出错

有一天, 当我点开我的colors.xml资源文件的时候,发现是下图这个样子

66201ae31114

colors.xml

然后当鼠标的光标移动到红色标记处,发现

The color “colorPrimary” in values has no declaration in the base values folder; this can lead to crashes when the resource is queried in a configuration that does not match this qualifier less…

接着我翻译了一下:

值中的颜色“colorPrimary”在基本值folde中没有声明

懵逼了,我不是声明了吗....最后还是百度到了结果

解决方式是:先把colors文件剪切下来,再粘回去。

感觉是AS的BUG....我用的AS版本是3.5.1

3.2 导入依赖的时候提示Failed to resolve

66201ae31114

Failed to resolve

当然百度了一下,解决方式是:在根目录的build.gradle中添加maven

就是下面这样

66201ae31114

image.png

3.3 在进入文章列表界面的时候,进度条不会自动隐藏

发生问题的场景:笔者在Presenter中请求文章列表的数据的时候,会自动显示和隐藏进度条,但请求完文章列表后,不能自动隐藏

经过浏览代码,发现,我的请求文章列表的方法写多了一次,解决方法:只保存onResume里面的一次

四.快捷体验

如果你想更加简单地使用这套框架,笔者特地为您准备了我已经封装好的 MVP 框架,你只需要导入依赖即可享受如上的快捷的开发体验,详情请见 github

五.在项目中运用

66201ae31114

image-20201026213715565

看完了,有些读者可能会有疑惑,说得那么牛逼,在项目中运用又是怎样吖,会不会有bug吖!!别急,笔者马上根据这个框架,开发了一个简单易用美观的『玩安卓』!希望能够解决您的困惑hhh

[图片上传失败...(image-74b509-1603720322753)]

66201ae31114

image-20201026214013321

如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值