MVI - 其实很简单

开局一张图,从来不变



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 层的关系,就像热水器一样接通上下水管道:


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 了

最后

没几行代码,本来也是没打算写文章出来的,但是后来想想网上都是用 mosby 库写的 MVI,我写出来给大家提供另一种思路吧,能简单的何必更复杂呢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342