MVI - 繼續(xù)來湊個熱鬧

MVI 概念

MVI 是和 MVVM 一起出現(xiàn)的概念,是跟著 Rxjava 響應式思路衍生出來的一種想法

MVVM 我猜大家都熟悉,數(shù)據(jù)層傳遞 Livedata -> persenter -> 再到 UI 層去注冊監(jiān)聽,這是個單向的過程,從 [數(shù)據(jù) -> UI] 的過程:



一般 MVVM 我們都是這么寫,但是也有的人寫的更徹底,app 的交互是個雙向過程,先從 UI -> 數(shù)據(jù) 再到 數(shù)據(jù) -> UI,上面常見的方式我們只是實現(xiàn)了一邊,更徹底的響應式改造是連點擊事件都是響應式的,數(shù)據(jù)層注冊監(jiān)聽按鈕的點擊事件,通過上水管道接受數(shù)據(jù),遠程數(shù)據(jù)返回后再通過下水管道返回數(shù)據(jù):


一般沒寫這么麻煩的~
MVI 和雙向 MVVM 的思路相同,區(qū)別就是 MVI 進一步抽象了 UI 的動作,也就是 MVI 中的 I - Intent,MVI 中把任何一個 UI 事件都看成一個響應式數(shù)據(jù)源,P 層的任務就是綁定注冊 V 和 M 層的關系,就像熱水器一樣接通上下水管道:

image.png

MVI 中的這個 Intent 有自己的思想,Intent 把 UI 頁面的任何變化都看成一個整體,用一個數(shù)據(jù)類型來表示,比如有一個 ViewState 對象里面有 loading,netError,success 各種表示頁面狀態(tài)的標志位,數(shù)據(jù)層返回的是這個 ViewState 而不再直接是數(shù)據(jù)了,UI 層根據(jù) ViewState 的狀態(tài)來顯示不同的 UI 樣式,這樣 P 層就不用再寫一堆控制 UI 顯示狀態(tài)的方法了,真正實現(xiàn)了 MVP 的分層思想,誰的事誰關心,當然 MVVM 也可以做到,但是一般 MVVM 里面都是直接返回數(shù)據(jù)的,真的把頁面狀態(tài)也封裝進數(shù)據(jù)的沒幾個人

class NetViewState(
        var loading: Boolean = false,
        var success: Boolean = false,
        var netError: Boolean = false,
        var dataError: Boolean = false,
        var dataNo: Boolean = false,
        var message: String = "",
        var data: BookResponse = BookResponse()) {


    companion object Help {

        @JvmStatic
        fun loading(): NetViewState {
            return NetViewState(loading = true)
        }

        @JvmStatic
        fun success(data: BookResponse): NetViewState {
            return NetViewState(success = true, data = data)
        }

        @JvmStatic
        fun netError(message: String): NetViewState {
            return NetViewState(netError = true, message = message)
        }

        @JvmStatic
        fun dataError(message: String): NetViewState {
            return NetViewState(dataError = true, message = message)
        }

        @JvmStatic
        fun dataNo(message: String): NetViewState {
            return NetViewState(dataNo = true,message = message)
        }
    }
}

代碼走起

MVI 我看了好多文章,都是借助 mosby 這個庫來實現(xiàn)的,mosby 帶來了大量的衍生類型,每個角色都有其基類,無形中大大增加了學習成本,MVI 本是 MVVM 思路的進一步而已,沒想到大伙做的反倒是越來越復雜,全完沒必要,簡簡單單的多好,還容易理解,容易閱讀
使用 rxjava 熱發(fā)射或是 Livedata 就可以簡單的實現(xiàn) MVI 了,我就不想用 mosby ,自己實現(xiàn)一個 MVI 出來,下面的 Demo 只是用來演示,更多的請自省封裝,優(yōu)化

Ui 層對外提供事件 Intent

    protected void onCreate(Bundle savedInstanceState) {
        btn_book.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bookIntent.onNext(new BookRequest("人生", "", "0", "10"));
            }
        });

        persenter.netActivity = this;
        persenter.bingIntent(bookIntent);
    }

    void updata(NetViewState netViewState) {

        if (netViewState.getLoading()) {
            Log.d("AA", "loading ...");
            return;
        }

        if (netViewState.getNetError()) {
            Log.d("AA", " netError:" + netViewState.getMessage());
        }

        if (netViewState.getDataError()) {
            Log.d("AA", " dataError:" + netViewState.getMessage());
        }

        if (netViewState.getSuccess()) {
            List<BookResponse.Book> books = netViewState.getData().getBooks();
            if (books != null && books.size() == 0) {
                ToastComponent.Companion.getInstance().show("沒有數(shù)據(jù)", Toast.LENGTH_SHORT);
            }
            adapter.refreshData(books);
        }
    }

M 層關注上游 UI 層事件,提供下游數(shù)據(jù)層觀察者

public class BookRepositroy {

    public static final String URL_BOOK_LIST = "book/search";
    public PublishSubject<NetViewState> bookData = PublishSubject.create();

    public void bingIntent(Observable<BookRequest> bookIntent) {
        bookIntent.subscribe(new Consumer<BookRequest>() {
            @Override
            public void accept(BookRequest bookRequest) throws Exception {
                getBookData(bookRequest);
            }
        });
    }

    private void getBookData(BookRequest bookRequest) {

        Map<String, String> map = new HashMap<>();
        map.put("q", bookRequest.getTitle());
        map.put("tag", bookRequest.getTag());
        map.put("start", bookRequest.getStartCount());
        map.put("count", bookRequest.getWantCount());

        HttpClient.Companion.getInstance().get(URL_BOOK_LIST, map)
                .map(new Function<ResponseBody, NetViewState>() {
                    @Override
                    public NetViewState apply(ResponseBody responseBody) throws Exception {
                        BookResponse bookResponse = null;
                        try {
                            bookResponse = new Gson().fromJson(responseBody.string(), BookResponse.class);
                        } catch (Exception e) {
                            Observable.error(e);
                        }
                        return NetViewState.success(bookResponse);
                    }
                })
                .onErrorReturn(new Function<Throwable, NetViewState>() {
                    @Override
                    public NetViewState apply(Throwable throwable) throws Exception {

                        if (throwable instanceof HttpException) {
                            //   HTTP錯誤
                            return NetViewState.netError("網絡錯誤");
                        } else if (throwable instanceof ConnectException
                                || throwable instanceof UnknownHostException) {
                            //   連接錯誤
                            return NetViewState.netError("連接錯誤");
                        } else if (throwable instanceof InterruptedIOException) {
                            //  連接超時
                            return NetViewState.netError("連接超時");
                        } else if (throwable instanceof JsonParseException
                                || throwable instanceof JSONException
                                || throwable instanceof ParseException) {
                            return NetViewState.dataError("解析錯誤");
                        } else if (throwable instanceof ApiException) {
                            return NetViewState.netError(throwable.getMessage());
                        } else if (throwable instanceof IOException) {
                            return NetViewState.netError("網絡錯誤");
                        }
                        return NetViewState.netError("位置錯誤");
                    }
                })
                .startWith(NetViewState.loading())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Consumer<NetViewState>() {
                    @Override
                    public void accept(final NetViewState netViewState) throws Exception {
                        bookData.onNext(netViewState);
                    }
                });
    }
}

p 層綁定上下游(V 和 P)關系

public class NetPersenter {

    public NetActivity netActivity;
    BookRepositroy repositroy = new BookRepositroy();

    public void bindBook(PublishSubject<BookRequest> bookIntent) {

        repositroy.bingIntent(bookIntent);
        repositroy.bookData.subscribe(new Consumer<NetViewState>() {
            @Override
            public void accept(NetViewState netViewState) throws Exception {
                netActivity.updata(netViewState);
            }
        });
    }
}
思考:
  • P 層注冊下游數(shù)據(jù)而沒有交給 V 層,是因為在 P 層里面我們可能有業(yè)務邏輯要要先一部處理,不能直接交給 U層
  • M 層使用 startWith 優(yōu)先發(fā)送一個 loading 的事件出來
  • ViewState 這里可以進一步優(yōu)化的,大部分頁面的狀態(tài)都一樣,有必要抽象一個基類出來,而且使用 koltin 的密封類(sealed) 還可以做的更好,再說現(xiàn)在是 kotlin 的時代了,純 java 的代碼我都不應該貼出來,這還是因為這個頁面還是以前寫的在上面改的,要不我就 kotlin 了

創(chuàng)作不易喜歡的話記得點贊+關注哦

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容