帶你封裝本身的MVP+Retrofit+RxJava2框架(二)

前言

本篇文章是針對上一篇文章:帶你封裝本身的MVP+Retrofit+RxJava2框架(一)的進一步封裝改進,建議在看完上一篇文章後,再食用本文效果更佳!java

文本已經收錄到個人Github我的博客,歡迎大佬們光臨寒舍:個人GIthub博客react

本篇文章須要已經具有的知識:

  • MVP的概念和基本使用
  • Retrofit框架的基本使用
  • RxJava2框架的基本使用
  • ButterKnife框架的基本使用
  • Base基類的概念
  • YUtils的簡單使用
  • BaseRecyclerViewAdapterHelper的簡單使用

學習清單:

  • Base實體類的封裝
  • Base異常類的封裝
  • Base觀察者的封裝
  • RxJava線程自動調度的小技巧
  • 進行網絡請求自動顯示加載中
  • 完成網絡請求自動關閉加載中
  • 自動處理異常信息
  • Cookie自動持久化與Retrofit的協同使用
  • 接口管理Retrofit請求接口的優美方式

一.爲何要封裝這套框架

​ 如上一篇文章所說,在MVP模式日漸流行的時候,封裝一套MVP框架,不只對平常的開發大大便利,還能提早積累一下將來在實際工做中的技巧,而且,良好的封裝和規範使用還能減小開發中的各類使人頭疼的BUG。android

​ 有人可能會問:「你上一篇不是也寫了MVP框架嗎?你這篇難道仍是同樣的嗎?難道你是換湯不換藥嗎?」git

​ 其實,一開始筆者也覺得我上一篇文章封裝的MVP框架已經夠不錯了,可是,在筆者某天看了yechaoa大神玩安卓java的源碼後,被其封裝的MVP框架的所折服,所以第一時間寫這篇文章,想向你們分享下,筆者從中吸收的經驗,但願可以幫助到各位!github

​ 本文相對上一篇文章的改進地方有下面幾點:json

  • 精簡了Activity基類,將原來的兩個BaseActivity和BaseMvpActivity精簡爲一個BaseActivity
  • 修復了當繼承了Activity基類,不添加presenter會致使空指針的bug
  • 添加了網絡請求可選擇自動顯示加載中和自動關閉加載中的功能
  • 添加了自動處理異常信息的功能
  • 封裝了一個Bean對象的基類
  • 精簡了RxJava的用法,所以能夠省去Model類的編寫
  • 封裝了一個Observer的基類
  • 增添了cookie自動持久化的功能
  • 改進RetrofitService的封裝,將Retrofit接口的實例化引入基類

二.核心用法與樣例分析

本項目基於Android X 進行構建,完整代碼可在個人github上下載:帶你封裝本身的MVP+Retrofit+RxJava2框架(二)api

首先,能夠看一下筆者項目的基本結構服務器

項目基本結構

爲了給你們模擬帶自動獲取Cookie的功能,因此筆者設計了一個具備登錄,註冊,收藏功能的Democookie

在這裏特別感謝玩安卓提供的API網絡

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'

    /*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<P extends BasePresenter> 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<P extends BasePresenter> 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<V extends BaseView> {

    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<T> extends DisposableObserver<T> {

    protected BaseView view;
    private boolean isShowDialog;

    public BaseObserver(BaseView view) {
        this.view = view;
    }

    /** * 帶進度條的初始化方法 * * @param view view * @param isShowDialog 是否顯示進度條 */
    public 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<T> implements Serializable {


    /** * data : * errorCode : 0 * errorMsg : */
    public T data;
    public int errorCode;
    public String errorMsg;
    
    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<Cookie> cookies) {
        if (cookies.size() > 0) {
            for (Cookie item : cookies) {
                cookieStore.add(url, item);
            }
        }
    }

    @Override
    public List<Cookie> 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<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    BaseResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        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 == -1001) {//失效
                    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<BaseBean<Article>> getArticleList(@Path("page") Integer page);


        //-----------------------【登陸註冊】----------------------

        //登陸
        @FormUrlEncoded
        @POST("user/login")
        Observable<BaseBean<User>> login(@Field("username") String username, @Field("password") String password);

        //註冊
        @FormUrlEncoded
        @POST("user/register")
        Observable<BaseBean<User>> register(@Field("username") String username, @Field("password") String password, @Field("repassword") String repassword);


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

        //收藏站內文章
        @POST("lg/collect/{id}/json")
        Observable<BaseBean> collectIn(@Path("id") Integer id);

        //取消收藏---文章列表
        @POST("lg/uncollect_originId/{id}/json")
        Observable<BaseBean> uncollect(@Path("id") Integer id);


    }

}
複製代碼

2.2.4 RetrofitService

Retrofit的配置類,在裏面初始化了apiServer對象,並配置了日誌信息,超時時間,Cookie持久化,用了靜態內部類的單例模式

* Description : RetrofitService
 *
 * @author XuCanyou666
 * @date 2020/2/8
 */


public class RetrofitService {

    private static RetrofitService apiRetrofit;
    private API.WAZApi apiServer;

    //單例調用
    public static RetrofitService getInstance() {
        if (apiRetrofit == null) {
            synchronized (Object.class) {
                if (apiRetrofit == null) {
                    apiRetrofit = new RetrofitService();
                }
            }
        }
        return apiRetrofit;
    }

    //獲取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(YUtils.getApplication()))
                .build();

        //關聯okhttp並加上rxjava和gson的配置和baseurl
        Retrofit retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(BaseConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(API.BASE_URL)
                .build();

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

}
複製代碼

2.3 bean

這裏的帶有嵌套的實體類看似很複雜,其實能夠經過一個在線JSON字符串轉Java實體類的工具進行生成,須要注意的是,不要將BaseBean的那一層導入到實體類中

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<DataDetailBean> 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 * <p> * 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總共3個模塊,在這裏僅說明一個模塊,其餘的模塊的寫法也是相似的,具體其餘模塊的寫法,能夠上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> user);

}
複製代碼
2.4.1.2 LoginPresenter

這裏由於RxJava通過封裝後,model層的代碼太少,因此將Model直接寫入Presenter中

/** * Description : LoginPresenter * * @author XuCanyou666 * @date 2020/2/8 */


class LoginPresenter extends BasePresenter<ILoginView> {

    LoginPresenter(ILoginView baseView) {
        super(baseView);
    }

    void login(String username, String password) {
        addDisposable(apiServer.login(username, password), new BaseObserver<BaseBean<User>>(baseView, true) {
            @Override
            public void onSuccess(BaseBean<User> bean) {
                baseView.showLoginSuccess("登陸成功( ̄▽ ̄)");
                baseView.doSuccess(bean);
            }

            @Override
            public void onError(String msg) {
                baseView.showLoginFailed(msg + "(°∀°)ノ");
            }
        });
    }
}

複製代碼
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<LoginPresenter> 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;

    private String mUsername;
    private String mPassword;

    @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(BaseBean<User> user) {
        //存進sp裏面
        SpUtil.setBoolean(GlobalConstant.IS_LOGIN, true);
        SpUtil.setString(GlobalConstant.USERNAME, user.data.username);
        SpUtil.setString(GlobalConstant.PASSWORD, user.data.password);
        startActivity(MainActivity.class, true);
    }

    /** * 判斷帳號和密碼輸入是否正確 * * @return */
    private boolean isValid() {
        mUsername = mEtUsername.getText().toString().trim();
        mPassword = mEtPassword.getText().toString().trim();
        return check(mUsername, mTilUsername) && check(mPassword, mTilPassword);
    }


    /** * 判斷輸入是否正確 * * @param string 輸入的內容 * @param textInputLayout textInputLayout控件 * @return */
    private boolean check(String string, TextInputLayout textInputLayout) {
        return !TextUtils.isEmpty(string) && string.length() <= textInputLayout.getCounterMaxLength() && textInputLayout.getCounterMaxLength() / 2 <= string.length();
    }

    @OnClick({R.id.btn_login, R.id.btn_register})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_login:
                YUtils.closeSoftKeyboard();
                if (isValid()) {
                    presenter.login(mUsername, mPassword);
                } else {
                    ToastUtil.showToast("填寫錯誤 (°∀°)ノ");
                }
                break;
            case R.id.btn_register:
                YUtils.closeSoftKeyboard();
                startActivity(RegisterActivity.class, false);
                break;
            default:
                break;
        }
    }
}

複製代碼

三.我在使用中遇到的問題

3.1 顏色的資源文件出錯

有一天, 當我點開個人colors.xml資源文件的時候,發現是下圖這個樣子

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

Failed to resolve

固然百度了一下,解決方式是:在根目錄的build.gradle中添加maven

就是下面這樣

image.png

3.3 在進入文章列表界面的時候,進度條不會自動隱藏

發生問題的場景,就是我在presenter中設置了請求文章列表的數據的時候,會自動顯示和隱藏進度條,可是請求完文章列表後,不能自動隱藏

通過瀏覽代碼,發現,個人請求文章列表的方法寫多了一次,解決方法:只保存onResume裏面的一次


若是文章對您有一點幫助的話,但願您能點一下贊,您的點贊,是我前進的動力

本文參考連接:

相關文章
相關標籤/搜索