首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
宝塔
V2EX  ›  Android

各位有遇到过 LiveData 在共享 ViewModel 中发生旧数据倒灌的情况吗?

  •  
  •   KunMinX · 58 天前 · 2406 次点击
    这是一个创建于 58 天前的主题,其中的信息可能已经有所发展或是发生改变。
    当我在 作用域 为 Activity 的 共享 ViewModel 中设置了一个 LiveData,

    FragmentA observe 这个 LiveData,那么在收到一次通知、并关闭 FragmentA 后,再进入 FragmentA,

    由于 该 ViewModel 是共享的,其中的 LiveData 并没有随着 FragmentA 的退出而清除,那么再次进入后,会自动走一遍 observe 回调,倒灌旧数据。

    我知道这么设计的缘由是为了处理生命周期从 unActive 到 active 转变时,跟进“最新” 的状态,但在共享 ViewModel 中,这算不算是一个 bug ?因为 LiveData 封装死了,我似乎没有其他办法来干预这种现象的发生。

    看看万能的 V 友有没什么好招
    29 回复  |  直到 2019-09-24 15:44:38 +08:00
        1
    bkmi   58 天前 via Android   ♥ 1
    既然 ViewModel 还在,说明数据还是有效的,并不是什么旧数据。
    如果你想把 ViewModel 和 FragmentA 绑定,那作用域就该用 Fragment 的
        2
    zyqf   58 天前 via Android
    其中的 LiveData 并没有随着 FragmentA 的退出而清除。

    我是不是可以理解为你的需求是 FragmentA 退出要清除 LiveData ?

    如果是的话,在 FragmentA 销毁阶段 postValue 空对象不就好了。
        3
    wsxyeah   58 天前 via iPhone
    类似于 BehaviorSubject
        4
    KunMinX   58 天前
    @bkmi @zyqf

    使用共享作用域,是为了能够跨页面通信,只不过第一次使用了,第二次它自动就回调了,不符合我预期。

    手动 postValue,容易因人工操作的疏忽而造成一致性问题,因为项目有数十个 Fragment,这种事情务必在后台自动完成。

    我刚刚找到的线索是,每次进入 fragment,livedata 的 observe.lastVersion 都被赋值为 -1,而 version 仍然是 0,那我觉得 bug 可能就在这里
        5
    KunMinX   58 天前   ♥ 1
    好消息,找到解决方案了。

    通过反射,阻止用户回调的 liveData 第一次创建时的推送。这个推送实在太坑了。

    http://www.pianshen.com/article/4994394892/
        6
    sanousun   58 天前   ♥ 1
    美团有一篇关于 LiveDataBus 的文章,你可以看看 https://tech.meituan.com/2018/07/26/android-livedatabus.html
        7
    Gehrman   58 天前
    viewModel 里加多个缓存,跟缓存一样的无视掉?
        8
    GLee9507   58 天前   ♥ 1
    我认为你是没有真正的理解 ViewModel 和 LiveData
        9
    HangoX   58 天前
    其实你直接在 ViewOnCreated 的时候 getLiveData.getValue 拿出来设置就好了,既然是一次性的东西
        10
    KunMinX   58 天前
    @HangoX

    就用 bus-LiveData 的方案了。

    不手动编写的原因,楼上我已经讲过。

    不直接用 LiveDataBus 的原因是,需要维持 通过 唯一可信源 分发状态的 开发模式,来避免不可预期、难以追溯和排查的错误。
        11
    bkmi   58 天前 via Android
    你这是把 LiveData 当 EventBus 用了啊…
        12
    KunMinX   58 天前
    我很后悔开了这个贴。

    除了 6 楼的 @sanousun 同学给出了建设性的答复,其他人始终慢了好几拍。

    我已经把状况讲得这么清楚了。

    为避免这些离题的回复 给后来者造成困扰,这里统一回复一下:

    任何技术绝非凭空存在,既然存在了,就有存在的缘由。

    ViewModel 被如此设计,当初就是有考虑过 使其作为跨页面共享的存在。

    当 lifeCyclerOwner 传入的是 fragment 的宿主 Activity,那么这个 ViewModel 就是被该 Activity 所持有、可以被旗下 Fragments 所共享。

    共享 ViewModel 的作用多是为了跨页面的通信,例如回调、实时通信等等。

    LiveData 的存在是为了确立 通过唯一可信源分发状态 的开发模式,以避免旧时候滥用 EventBus 导致的 难以追溯、数据过时 等问题。
    同时在 ViewModel 或单例的配合下,它使单向依赖成为了可能。
    再者它也可以规避一些生命周期问题。

    这也就是为什么我选择标准化的 Jetpack MVVM 架构来完成状态管理。

    除了状态管理,MVVM 同样有这个条件,甚至是更好的条件,来支持消息推送。这也就是为什么我要用 LiveData 来实现跨页面通信,而拒绝使用 EventBus。

    而既然考虑到 共享 ViewModel,那么 LiveData 也理应考虑到应解除对默认 粘性设计的支持。

    这个粘性设计或许在 视图控制器 重建 恢复状态时有用,在消息通信时则是个麻烦,所以我说这是个 bug,务必解决。
        13
    KunMinX   58 天前
    当然,上面我说到了 “ViewModel” 包含共享的考虑,肯定会有杠精不放过一丝添堵的机会。

    首先,遇到像 8 楼这种纯粹来恶心人、不就事论事 的评论,我第一个就选择 block,

    其次,我可以分享一下自己在项目中是怎么使用 ViewModel 的:

    因为 ViewModel 的本职主要是状态的托管,可以托管 DataBinding 或 视图控制器 所依赖的 state,
    也即这种 ViewModel 是 fragment 独享的,我会为每个 Fragment 都配一个,反正我有自己编写的自动化脚本工具,配几个 state 字段就能生成对应的 ViewModel。

    第二种就是专门用于数据请求,这些我也设计成让他们被 fragment 独占。因为 ViewModel 就是设计为,你传入什么 owner,这个 ViewModel 就是被哪个 owner 所持有。而且反正就算独占,fragment 重建时这个 ViewModel 被 retain 保留了因而可以支持视图状态的恢复。

    第三种就是本帖提到的这种消息通信的用法。

    我就说这么多了。
        14
    GLee9507   58 天前
    @KunMinX #13 没恶心你,说的是实话,真的。
        15
    maninfog   58 天前 via iPhone   ♥ 1
    @GLee9507 哇 你这种人真的恶心到我😫 本来就是寻求讨论的,你有何高见倒是说出来阿,啥建设性意见没有上来就是一句别人不懂,被怼了还要继续杠,shame on you !
        16
    colaman   58 天前
    @KunMinX 我当初是自己写了一个包装类处理这个粘性问题,我是觉得这种属性 Google 应该给开发者自己决定才比较合理
        17
    Didadida   58 天前
    试试 SingleLiveEvent ?
        18
    mxalbert1996   58 天前 via Android   ♥ 1
    @KunMinX 说到底就是 LiveData 不是为了你这种用途而设计的。LiveData 相当于 Rx 里的 BehaviorSubject,而你却想把它当 Observable 用。
    另外解决这种问题还要用反射也太不优雅了吧。写个新类继承 MutableLiveData,覆写 observe 方法在最前面加一条 setValue(null)不就行了?
        19
    zeroDev   58 天前 via Android   ♥ 1
    @maninfog 网友没有教会你的责任,指出来哪里有问题就是好心了
        20
    KunMinX   58 天前
    @colaman

    是的。类似的令人头疼的问题也存在于 Navigation。

    Navigation 写死了切换 fragment 必须是通过 replace,而不能使用者自己选择,所以当前对于 Navigation 态度,也是保持欣赏但观望的态度(对它的声明式编程理念比较欣赏)。

    网上有给出 keep-fragment-state-Navigation 的解法,这里顺带分享一下:

    https://github.com/STAR-ZERO/navigation-keep-fragment-sample

    https://github.com/lwj1994/navigation-keep-state-fragment

    @mxalbert1996

    反射的方式影响最小。因为目标只是 为了解决 不自动倒灌 旧数据,要是先 set Null 了,那么其他已观察的页面再想要取 liveData 临时存的数据,就莫得治了
        21
    KunMinX   58 天前
    @KunMinX
    笔误,应该说,要是先 set Null 了,那么其他已观察的页面 就有被迫接受一次推送 null 的风险了。(因为毕竟是 一对多的推送、共享的 ViewModel 和 liveData 的情况)

    数十个页面,人工操作一定会存在疏忽、需要各种判空,所以这样其实从快速开发的角度来看,埋下的隐患要大一点。
        22
    KunMinX   58 天前
    @KunMinX
    唉,感觉这个答案还可以更恰当一点:observe 前 set null 一次,容易造成 其他页面 遭受容易被使用者疏忽的 被迫推送,而产生不可预期的错误。

    感谢 @mxalbert1996 提出的观点,让我不断地反思 设计成 反射方式 其背后的考虑。
        23
    GLee9507   58 天前 via Android
    @maninfog 我表达了一下我的主观想法,我只是主观的认为楼主理解有误,或许楼主可以另辟蹊径分析问题发生的原因。造成大家不爽深感抱歉。
        24
    janus77   58 天前 via iPhone
    问个沾边的问题,livedata 能用做 application 全局缓存么?
        25
    DioV   57 天前
    “在消息通信时则是个麻烦,所以我说这是个 bug,务必解决。”
    因为 LiveData 就不是为了解决通讯而存在的,哪里谈得上 bug...
    举个不恰当的例子,就像用 Retrofit 做 gRPC 的请求,能实现但绝对不是最合适的方式。
        26
    KunMinX   57 天前 via iPhone
    @DioV 断章取义烦不烦... MVVM 是一个整体,既然 ViewModel 被设计为支持共享作用域,那么 LiveData 就应考虑到这种跨页面通信的情况,这是官方文档自己都承认的需求。现在我就给 Google 小组留言这个问题。
        27
    KunMinX   57 天前
    楼上 @colaman 同学提到的包装类的办法,经调查,如果是我这样依然托管给 ViewModel 从而维持 唯一可信源状态分发 的话,那包装类的方式其实更合适。

    如果是用作纯粹的 BUS,那可以考虑反射的方式。

    这里分享一篇包装类处理的办法:

    https://juejin.im/post/5b2b1b2cf265da5952314b63
        28
    DioV   57 天前
    @KunMinX
    戾气真重...
    如果你理解下的 MVVM 必须提供这样的功能。那我是不是可以理解 Jetpack Compose 作为一套 Declarative Toolkit 而不必实现这样的需求?
    当然我希望你的 Feature Request 能被官方接受。毕竟我也只是个想说说自己看法的围观群众🤷‍♂️
        29
    KunMinX   57 天前
    @DioV

    统一回复一下:

    我对自己的每一条评论负责。

    通过讨论,我不仅要解决自己的问题,也要考虑到对后来者有无帮助。

    在和就事论事的 V 友的讨论中,对 LiveData 粘性问题的解决方案得以不断刷新,因而这次讨论算是成功的,有价值的。

    对于 LiveData 的状况,我在 12 楼给出了多达 500 字的翔实的分析,依然还有人只顾自己爽,来不及阅读理解,就急忙发表一些断章取义的抬杠,事后又以各种理由推卸责任。🤷‍♂️
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3538 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 35ms · UTC 01:09 · PVG 09:09 · LAX 17:09 · JFK 20:09
    ♥ Do have faith in what you're doing.