美文网首页
MVI - 继续来凑个热闹

MVI - 继续来凑个热闹

作者: Android高级工程师 | 来源:发表于2019-05-20 14:55 被阅读0次

    MVI 概念

    MVI 是和 MVVM 一起出现的概念,是跟着 Rxjava 响应式思路衍生出来的一种想法

    MVVM 我猜大家都熟悉,数据层传递 Livedata -> persenter -> 再到 UI 层去注册监听,这是个单向的过程,从 [数据 -> UI] 的过程:



    一般 MVVM 我们都是这么写,但是也有的人写的更彻底,app 的交互是个双向过程,先从 UI -> 数据 再到 数据 -> UI,上面常见的方式我们只是实现了一边,更彻底的响应式改造是连点击事件都是响应式的,数据层注册监听按钮的点击事件,通过上水管道接受数据,远程数据返回后再通过下水管道返回数据:


    一般没写这么麻烦的~
    MVI 和双向 MVVM 的思路相同,区别就是 MVI 进一步抽象了 UI 的动作,也就是 MVI 中的 I - Intent,MVI 中把任何一个 UI 事件都看成一个响应式数据源,P 层的任务就是绑定注册 V 和 M 层的关系,就像热水器一样接通上下水管道:

    image.png

    MVI 中的这个 Intent 有自己的思想,Intent 把 UI 页面的任何变化都看成一个整体,用一个数据类型来表示,比如有一个 ViewState 对象里面有 loading,netError,success 各种表示页面状态的标志位,数据层返回的是这个 ViewState 而不再直接是数据了,UI 层根据 ViewState 的状态来显示不同的 UI 样式,这样 P 层就不用再写一堆控制 UI 显示状态的方法了,真正实现了 MVP 的分层思想,谁的事谁关心,当然 MVVM 也可以做到,但是一般 MVVM 里面都是直接返回数据的,真的把页面状态也封装进数据的没几个人

    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 这个库来实现的,mosby 带来了大量的衍生类型,每个角色都有其基类,无形中大大增加了学习成本,MVI 本是 MVVM 思路的进一步而已,没想到大伙做的反倒是越来越复杂,全完没必要,简简单单的多好,还容易理解,容易阅读
    使用 rxjava 热发射或是 Livedata 就可以简单的实现 MVI 了,我就不想用 mosby ,自己实现一个 MVI 出来,下面的 Demo 只是用来演示,更多的请自省封装,优化

    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("没有数据", Toast.LENGTH_SHORT);
                }
                adapter.refreshData(books);
            }
        }
    

    M 层关注上游 UI 层事件,提供下游数据层观察者

    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 层注册下游数据而没有交给 V 层,是因为在 P 层里面我们可能有业务逻辑要要先一部处理,不能直接交给 U层
    • M 层使用 startWith 优先发送一个 loading 的事件出来
    • ViewState 这里可以进一步优化的,大部分页面的状态都一样,有必要抽象一个基类出来,而且使用 koltin 的密封类(sealed) 还可以做的更好,再说现在是 kotlin 的时代了,纯 java 的代码我都不应该贴出来,这还是因为这个页面还是以前写的在上面改的,要不我就 kotlin 了

    创作不易喜欢的话记得点赞+关注哦

    相关文章

      网友评论

          本文标题:MVI - 继续来凑个热闹

          本文链接:https://www.haomeiwen.com/subject/egskzqtx.html