前言
本篇文章是针对上一篇文章:带你封装自己的MVP+Retrofit+RxJava2框架(一)的进一步封装改进,建议在看完上一篇文章后,再食用本文效果更佳!
本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:我的GIthub博客
本篇文章需要已经具备的知识:
-
MVP
的概念和基本使用 -
Retrofit
框架的基本使用 -
RxJava2
框架的基本使用 -
ButterKnife
框架的基本使用 -
Base
基类的概念 - YUtils的简单使用
- BaseRecyclerViewAdapterHelper的简单使用
学习清单:
-
Base
实体类的封装 -
Base
异常类的封装 -
Base
观察者的封装 -
RxJava
线程自动调度的小技巧 - 进行网络请求自动显示加载中
- 完成网络请求自动关闭加载中
- 自动处理异常信息
-
Cookie
自动持久化与Retrofit
的协同使用 - 接口管理
Retrofit
请求接口的优美方式
一.为什么要封装这套框架
如上一篇文章所说,在MVP
模式日渐流行的时候,封装一套MVP
框架,不仅对日常的开发大大便利,还能提前积累一下未来在实际工作中的技巧,并且,良好的封装和规范使用还能减少开发中的各种令人头疼的BUG。
有人可能会问:“你上一篇不是也写了MVP
框架吗?你这篇难道还是一样的吗?难道你是换汤不换药吗?”
其实,一开始笔者自以为我上一篇文章封装的MVP
框架已经够不错了,但是,在笔者某天看了yechaoa大神玩安卓java的源码后,被其封装的MVP
框架的所折服,因此第一时间写这篇文章,想向大家分享下,笔者从中汲取的经验,希望能够帮助到各位!
本文相对带你封装自己的MVP+Retrofit+RxJava2框架(一)的改进地方有下面几点:
- 精简了Activity基类,将原来的两个
BaseActivity
和BaseMvpActivity
精简为一个BaseActivity
- 修复了当继承了
Activity
基类,不添加Presenter
会导致空指针的Bug
- 添加了网络请求可选择自动显示加载中和自动关闭加载中的功能
- 添加了自动处理异常信息的功能
- 封装了一个
Bean
对象的基类 - 精简了RxJava的用法,因此可以省去
Model
类的编写 - 封装了一个
Observer
的基类 - 增添了cookie自动持久化的功能
- 改进了
RetrofitService
的封装,将Retrofit
接口的实例化引入基类
二.核心用法与样例分析
本项目基于
Android X
进行构建,完整代码已经上传到我的Github仓库
首先,先给大家介绍下笔者项目的基本结构
为了给大家模拟带自动获取Cookie的功能,所以笔者设计了一个具有登陆,注册,收藏功能的Demo
在这里特别感谢玩安卓提供的API
笔者在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:[email protected]
*/
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
资源文件的时候,发现是下图这个样子
然后当鼠标的光标移动到红色标记处,发现
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
当然百度了一下,解决方式是:在根目录的build.gradle
中添加maven
就是下面这样
3.3 在进入文章列表界面的时候,进度条不会自动隐藏
发生问题的场景:笔者在
Presenter
中请求文章列表的数据的时候,会自动显示和隐藏进度条,但请求完文章列表后,不能自动隐藏
经过浏览代码,发现,我的请求文章列表的方法写多了一次,解决方法:只保存onResume
里面的一次
四.快捷体验
如果你想更加简单地使用这套框架,笔者特地为您准备了我已经封装好的 MVP
框架,你只需要导入依赖即可享受如上的快捷的开发体验,详情请见 github
github
地址:https://github.com/LoveLifeEveryday/XMvp
五.在项目中运用
看完了,有些读者可能会有疑惑,说得那么牛逼,在项目中运用又是怎样吖,会不会有bug吖!!别急,笔者马上根据这个框架,开发了一个简单易用美观的『玩安卓』!希望能够解决您的困惑hhh
[图片上传失败...(image-74b509-1603720322753)]
如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力
本文参考链接:
- wanandroid_java
- android studio 中colors文件的bug
- Android 基类BaseActivity的封装
- 玩Android 开放API
- 依赖包后出现Failed to resolve:com.github.xxx错误