LiveData 数据倒灌:别问,问就是不可预期 - Perfect alternative to SingleLiveEvent, supporting multiple observers.

Overview

前言

大家好,我是《Jetpack MVVM Best Practice》作者 KunMinX。

今天提到的 “数据倒灌” 一词,缘于我为了方便理解和记忆 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况,而在 2019 年 自创并在网上传播的 关于此类现象的概括

它主要发生在:通过 SharedViewModel + LiveData 的组合 来解决页面通信的场景。

 

背景

由于本文的目标主要是来介绍 官方 Demo 现有解决方案的缺陷,以及经过 2 年迭代的趋于完美的解决方案,

所以我们假设在座的诸位 对最基本的背景缘由有一定的了解,知道:

为什么 LiveData 默认被设计为粘性事件

为什么 官方文档 推荐使用 SharedViewModel + LiveData(文档没明说,但事实上包含三个关键的背景缘由)

乃至为什么存在 “数据倒灌” 的现象

以及为什么在 “页面通信” 的场景下,不用静态单例、不用 LiveDataBus

如果对于这些前置知识也尚不了解,可结合个人的兴趣前往《LiveData 数据倒灌 背景缘由全貌 独家解析》查阅,此处不再累述。

 

现有解决方案及各自缺陷

《Jetpack MVVM 精讲》中我们分别提到了 Event 事件包装器、反射方式、SingleLiveEvent 这三种方式来解决 “数据倒灌” 的问题。它们分别来自上文我们提到的外网美团的文章,和官方最新 demo

但正如我们在《Jetpack MVVM 精讲》介绍的,它们分别存在如下问题:

Event 事件包装器:

对于多观察者的情况,只允许第一个观察者消费,这不符合现实需求;

而且手写 Event 事件包装器,在 Java 中存在 null 安全的一致性问题。

反射干预 Version 的方式:

存在延迟,无法用于对实时性有要求的场景;

并且数据会随着 SharedViewModel 长久滞留在内存中得不到释放。

官方最新 demo 中的 SingleLiveEvent:

是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;

而且额外引入了消息未能从内存中释放的问题。

 

UnPeek-LiveData 特点

1.一条消息能被多个观察者消费(since v1.0)

2.消息被所有观察者消费完毕后才开始阻止倒灌(since v4.0)

3.可以通过 clear 方法手动将消息从内存中移除(since v4.0)

4.让非入侵设计成为可能,遵循开闭原则(since v3.0)

5.基于 "访问权限控制" 支持 "读写分离",遵循唯一可信源的消息分发理念(since v2.0,详见 ProtectedUnPeekLiveData)

并且 UnPeekLiveData 提供构造器模式,后续可通过构造器组装适合自己业务场景的 UnPeekLiveData。

UnPeekLiveData<Moment> test =
  new UnPeekLiveData.Builder<Moment>()
    .setAllowNullValue(false)
    .create();

 

Maven 依赖

implementation 'com.kunminx.arch:unpeek-livedata:7.2.0-beta1'

温馨提示:

1.上述 implementation 的命名,我们已从 archi 改为 arch,请注意修改,

2.鉴于 Jcenter 的关闭,我们已将仓库迁移至 Maven Central,请自行在根目录 build.gradle 添加 mavenCentral()

 

Thanks

PS:非常感谢近期 hegaojian、Angki、Flynn、Joker_Wan、小暑知秋、大棋、空白、qh、lvrenzhao、半节树人 等小伙伴积极的试用和反馈,使得未被觉察的问题 被及时发现和纳入考虑。

 

谁在使用

通过小伙伴的私下反馈和调查问卷我们了解到

包括 “腾讯音乐、BMW、TCL” 在内的诸多知名厂商的软件,都参考过我们开源的 Jetpack MVVM Scaffold 架构模式,以及正在使用我们维护的 UnPeek-LiveData 等框架。

问卷调查我们长期保持对外开放,如有意可自行登记,以便吸引更多小伙伴 参与到对这些架构组件的 使用、反馈,集众人之所长,让架构组件得以不断演化和升级。

集团 / 公司 产品
腾讯音乐 即将更新,敬请期待
TCL 内置应用,暂时保密
左医科技 诊室听译机器人
BMW Speech
上海互教信息有限公司 知心慧学教师
美术宝 弹唱宝
网安

 

历史版本

版本 更新日期
UnPeekLiveData v7.2 2021.8.20
UnPeekLiveData v7.1 2021.8.16
UnPeekLiveData v7 2021.8.10
UnPeekLiveData v6.1 2021.7.15
UnPeekLiveData v6 2021.6.17
UnPeekLiveData v5 2021.4.21
UnPeekLiveData v4.4 2021.1.27
UnPeekLiveData v4.2 2020.12.15
UnPeekLiveData v4 2020.10.16

 

最新更新动态

UnPeekLiveData v7.2 特点

感谢小伙伴 @RebornWolfmanissue 的分享,通过更简便的方式来修复 v7.0 潜在的 removeObserver 内存泄漏问题。

 

UnPeekLiveData v7.1 特点

修复 removeObserver 潜在的内存泄漏。

 

UnPeekLiveData v7.0 特点

感谢小伙伴 @RebornWolfmanissue 中的分享,

相较于上一版,我们在 V7 版源码中,通过在 "代理类/包装类" 中自行维护一个版本号,在 UnPeekLiveData 中维护一个当前版本号,分别来在 setValue 和 Observe 的时机来改变和对齐版本号,如此使得无需另外管理一个 Observer map,从而进一步规避了内存管理的问题,

同时这也是继 V6 版源码以来,最简的源码设计,方便阅读理解和后续修改。

具体可参见 UnPeekLiveData 最新源码及注释的说明。

 

UnPeekLiveData v6.1 特点

感谢小伙伴 @liweiGeissue 中的启发,

相较于上一版,我们在 V6.1 版源码中,将 state 演变为 ObserverProxy 的字段来管理,从而将 Map 合二为一,让代码逻辑进一步简化。

 

UnPeekLiveData v6.0 特点

感谢小伙伴 @wl0073921 对 UnPeekLiveData 源码的演化做出的贡献,

V6 版源码翻译和完善自小伙伴 wl0073921 在 issue 中的分享,

V6 版源码相比于 V5 版的改进之处在于,引入 Observer 代理类的设计,这使得在旋屏重建时,无需通过反射方式跟踪和复用基类 Map 中的 Observer,转而通过 removeObserver 的方式来自动移除和在页面重建后重建新的 Observer,

因而复杂度由原先的分散于基类数据结构,到集中在 proxy 对象这一处,进一步方便了源码逻辑的阅读和后续的修改。

具体可参见 UnPeekLiveData 最新源码及注释的说明。

 

UnPeekLiveData v5.0 特点

感谢就职于 “腾讯音乐部门” 的小伙伴 @zhangjianlaoda 应邀对 UnPeekLiveData 做的优化和升级。

该版本保留了 UnPeekLiveData v4 的下述几大特点,并在适当时机基于反射等机制,来彻底解决 UnPeekLiveData v4 下 Observers 无法释放、重复创建,以及 foreverObserver、removeObserver 被禁用等问题,将 UnPeekLiveData 的内存性能再往上提升了一个阶梯。

同时,该版本使 Observe 等方法的方法名和形参列表与官方 API 保持一致,尽可能减少新上手小伙伴的学习成本。

具体可参见 UnPeekLiveData 最新源码及注释的说明。

 

UnPeekLiveData v4.0 特点

我们在 UnPeekLiveData v3.0 的基础上,参考了小伙伴 Flywith24 - WrapperLiveData 遍历 ViewModelStore 的思路,以此提升 “防止倒灌时机” 的精准度。

注:出于在现有 AndroidX 源码的背景下实现 "防倒灌机制" 的需要,v4.0 对 Observe 方法的使用做了微调,改为分别针对 Activity/Fragment 提供 ObserveInActivity 和 ObserveInFragment 方法,具体缘由详见源码注释的说明。

 

UnPeekLiveData v3.0

Update since 2020.7.10

通过 独创的 “延时自动清理消息” 的设计,来满足:

1.消息被分发给多个观察者时,不会因第一个观察者消费了而直接被置空

2.时限到了,消息便不再会被倒灌

3.时限到了,消息自动从内存中清理释放

4.使非入侵的设计成为可能,并最终结合官方 SingleLiveEvent 的设计实现了 遵循开闭原则的非入侵重写

 

UnPeekLiveData v2.0

Update since 2020.5

1.结合 Event 包装类的使用,对 LiveData 类进行入侵性修改。

2.提供 ProtectedUnPeekLiveData,基于访问权限控制实现 "读写分离":支持只从 "唯一可信源"(例如 ViewModel)内部发送、而 Activity/Fragment 只允许 Observe。

 

UnPeekLiveData v1.0

Update since 2019

1.针对 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况 创建 “数据倒灌” 的定义,并在网上交流和传播。

2.参考美团 LiveDataBus 的设计,透过反射的方式拦截并修改 Last Version 来防止倒灌。

 

License

本文以 CC 署名-非商业性使用-禁止演绎 4.0 国际协议 发行。

Copyright © 2019-present KunMinX

文中提到的 对 “数据倒灌” 一词及其现象的概括、对 Event 事件包装器、反射方式、SingleLiveEvent 各自存在的缺陷的理解,以及对 UnPeekLiveData 的 “延迟自动清理消息” 的设计,均属于本人独立原创的成果,本人对此享有最终解释权。

文中 "最新更新动态" 处提到的新版源码设计,以及对新版源码思路的理解和解析,属于参与过新版设计讨论的有效贡献者及本人的共同成果,我们对此享有所有权和最终解释权。

任何个人或组织在引用上述内容时,须注明原作者和链接出处。未经授权不得用于洗稿、广告包装、卖课等商业用途。

Copyright 2019-present KunMinX

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • ProtectedUnPeekLiveDataV5疑问

    ProtectedUnPeekLiveDataV5疑问

    你好,我在阅读源码试着理解版本代码演进的逻辑,但是我发现 V5版本还是存在HashMap恒久存在,注册的 Observer 越多,占用的内存越大问题 image image image

    发现在退出页面调用removeObserver 方法时,入参 observer 进行 Hash 运算得到的 observeKey 与注册 Observer 时得到的observeKey 不一样

    opened by Lumberjack100 3
  • ViewModel has no zero argument constructor

    ViewModel has no zero argument constructor

    使用 getApplicationScopeViewModel(Class modelClass) 方法初始化 ViewModel 时,如果传入的是一个 AndroidViewModel,会报 java.lang.Class<com.kunminx.puremusic.domain.message.SharedViewModel> has no zero argument constructor

    想请教下 AndroidViewModel 怎么使用 Application context 初始化?

    opened by Lumberjack100 2
  • 其实根本没有所谓的数据倒灌, 我这里想做一个解释

    其实根本没有所谓的数据倒灌, 我这里想做一个解释

    描述问题

    我是一个 MVVM 架构和响应式编程深度使用的一个开发者. Github 上刷到你的这个仓库. 我还是想对你们所说的数据倒灌进行解释说明一下.

    你所说的数据倒灌其实根本不是一个问题或者 bug. LiveData 设计就是如此. 接受最近一个信号. 对应流的 Behavior 模式. 我们有知名度一点的流的实现有 RxJava 和 Kotlin 的 Flow. 在他们的实现中, 分别对应 BehaviorSubject 和 StateFlow 他们的图示如下, 你们可以看到, 在不同的时间点发生订阅, 你总是能收到最近的一个信号. 除非一开始就没发射过信号. 而 LiveData 正是类似于此种模式. 所以你们说它的数据倒灌, 其实根本不是问题, 人家设计本是如此.

    那你们说的数据倒灌根本原因是因为什么呢? 其实是因为你们监听了 Behavior 模式的流或者 LiveData 去做了相应的操作. 比如你监听一个 LiveData 去做了网络请求 当你界面第一次进入, LiveData 中产生了一个信号, 你收到之后做了一次请求, 后来由于系统配置更改引起界面重建, 但是 ViewModel 还是原先那个, 所以在界面重建后你去监听 LiveData, 就会立马收到一个信号, 导致你又做了一次请求. 这里说明的场景, 是由于错误使用了 LiveData 引起的. 如果你要监听一个信号做一定的行为, 这类通常是需要监听 Publish 模式的流. 而 LiveData 设计之初就是 Behavior 模式, Publish 模式的行为示意图如下, 你只能收到你订阅点之后的信号.

    总结

    • Behavior 模式可选的方案
      • RxJava 的 BehaviorSubject
      • Kotlin Flow 的 StateFlow
      • LiveData
    • Publish 模式可选的方案
      • RxJava 的 PublishSubject
      • Kotlin Flow 的 SharedFlow
      • 我们自定义的 Listener 等

    综上所述, 在响应式编程中, 由于你接受的信号源有不同的模式实现. 所以在平常的业务需求中, 我们也要合理的进行选择.

    比如我们用于显示界面的场景, Behavior 模式是最适合不过了, 这也是为什么 LiveData 出现的原因. 本就为了显示页面的数据去的. 在界面重建也能重新进行显示.

    再比如我们用于执行某些行为的场景, 比如你收到一个信号进行一次网络请求、数据库操作、跳转等等. 这些其实都是需要使用 Publish 模式的.

    希望我在这里的长篇大论, 能很好的解释你们出现的所谓的数据倒灌的问题. 在项目中能合理的选择对应的实现去解决问题. 并且对一个响应式的数据源进行监听的时候, 需要先知道它的实现模式是 Behavior 还是 Publish, 以便于你做出判断, 可以用作哪些场景的使用

    opened by xiaojinzi123 2
  • UnPeekLiveData能否读写分离

    UnPeekLiveData能否读写分离

    只能在ViewModel中写数据。

    private val _phoneInput: MutableLiveData<String>
    
    val phoneInput: LiveData<String>
            get() = _phoneInput
    

    外部只能拿到phoneInput,只能observe。 希望UnPeekLiveData也能做相应操作。

    opened by qiushui95 2
  • 倒灌问题的理解

    倒灌问题的理解

    倒灌问题的产生原因:1.重新注册监听,2.livedata有数据。如果livedata用来发送事件场景时,应该是只关心添加监听之后的数据,添加之前的数据是不关心的。 倒灌问题的解决方案的思考:1.数据的管理者可能不关心数据的消费者是要什么样的数据(粘性、还是非粘性),2.livedata 的行为结果整个应用内应该是一致。因此我认为扩展方法比修改原方法可能更合适。 不成熟的解决方案:

    /**
     * LiveData扩展事件监听,去掉粘性,去掉空数据
     * 订阅之前的数据不会回调。只会收到非空的数据。只在前台是收到回调,自动反注册
     */
    fun <T> LiveData<T>.observeEvent(owner : LifecycleOwner,observer : Observer<T>){
        val ob = ObserverWrapper(this,observer)
        this.observe(owner,ob)
    }
    
    /**
     * 包装一下,用于判断是不是添加监听之后的数据,之前的数据不回调,空数据不回调
     * 事件不能为空
     */
    internal class ObserverWrapper<T>(liveData: LiveData<T>,private val mObserver: Observer<in T>) : Observer<T> {
        private var skipValue = liveData.value !=null
        override fun onChanged(t: T) {
            if (!skipValue && t != null) {
                mObserver.onChanged(t)
            }
            skipValue = false
        }
    
        override fun equals(o: Any?): Boolean {
            if (this === o) {
                return true
            }
            if (o == null || javaClass != o.javaClass) {
                return false
            }
            val that = o as ObserverWrapper<*>
            return mObserver == that.mObserver
        }
    
        override fun hashCode(): Int {
            return Objects.hash(mObserver)
        }
    }
    
    opened by 3298837796 1
  • 其实这样就可以了,不需要包一层

    其实这样就可以了,不需要包一层

    open class ProtectedUnPeekLiveData<T : Any?> : LiveData<T?>() {
    
        private val pending = AtomicBoolean(false)
        protected var isAllowNullValue = false
    
        override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
            super.observe(owner) { t ->
                if (pending.compareAndSet(true, false)
                    && (t != null || isAllowNullValue)
                ) {
                    observer.onChanged(t)
                }
            }
        }
    
        override fun observeForever(observer: Observer<in T?>) {
            super.observeForever { t ->
                if (pending.compareAndSet(true, false)
                    && (t != null || isAllowNullValue)
                ) {
                    observer.onChanged(t)
                }
            }
        }
    
        fun observeSticky(owner: LifecycleOwner, observer: Observer<T?>) {
            super.observe(owner, observer)
        }
    
        fun observeStickyForever(observer: Observer<T?>) {
            super.observeForever(observer)
        }
    
        override fun setValue(value: T?) {
            if (value != null || isAllowNullValue) {
                pending.set(true)
                super.setValue(value)
            }
        }
    
        fun clear() {
            super.setValue(null)
        }
    }
    
    opened by jackjustbj 1
  • 这段代码写的有问题

    这段代码写的有问题

    public UnPeekLiveData create() { UnPeekLiveData liveData = new UnPeekLiveData<>(); liveData.isAllowNullValue = this.isAllowNullValue; return liveData; }

    UnPeekLiveData 没有这个isAllowNullValue 变量。

    opened by bingdu0 1
  • 重构 UnPeekLiveData,最终实现对谷歌原生 LiveData 完全无侵入性的目的。在多人协作的场景下,其他同学就只需要理解 U…

    重构 UnPeekLiveData,最终实现对谷歌原生 LiveData 完全无侵入性的目的。在多人协作的场景下,其他同学就只需要理解 U…

    重构 UnPeekLiveDataV4版本

    最终实现对谷歌原生 LiveData 完全无侵入性的目的。在多人协作的场景下,其他同学就只需要理解 UnPeekLiveData 能够解决粘性事件、数据倒灌问题,其他的完全不需要理解,用法上完全跟原生 LiveData 保持一致

    问题一:UnPeekLiveData 中的 observers 这个 HashMap 恒久存在,注册的 Observer 越多,占用的内存越大,并且除非UnPeekLiveData 被回收,否则恒久存在内存当中

    解决:在 removeObserver 方法中移除 map 中对应存储的 storeId

    问题二:无法通过 removeObserver 方法移除指定的 Observer(某些场景需要提前 removeObserver)

    解决:通过维护外部传入 Observer 与内部代理 Observer 的映射关系,在 removeObserver 调用时,通过反射找到真正注册到 LiveData 中的 Observer,实现移除

    问题三:同一个 Observer 对象,注册多次,UnPeekLiveData 内部实际上会注册了多个不同的 Observer,从而导致重复回调,产生一些不可预期的问题

    解决:内部不会每次调用 observe 方法时都新创建一个代理 Observer,而是复用已经存在的代理 Observer(注意!!!Kotlin + LiveData + Lamda 由于编译器优化,可能会抛 Cannot add the same observer with different lifecycles 异常)

    问题四:无法使用 observerForever 方法

    解决:UnPeekLiveData 内部直接持有 forever 类型的 Observer

    opened by zhangjianlaoda 0
  • 7.1内存泄漏

    7.1内存泄漏

    当使用 removeObservers(@NonNull final LifecycleOwner owner) 的时候,回调过来已经是ObserverWrapper,又封装了一层,导致没有删除掉。应该是加一个判断,判断回调当前类是否已经是ObserverWrapper的类型,不是才需要包装删除,那时候没有测试想到。 @Override public void removeObserver(@NonNull @NotNull Observer<? super T> observer) { if (observer.getClass().isAssignableFrom(ObserverWrapper.class)) { super.removeObserver(observer); } else { super.removeObserver(createObserverWrapper(observer, START_VERSION, false)); } }

    精华 
    opened by RebornWolfman 6
  • 写了个简版的防止倒灌,不知道行不行

    写了个简版的防止倒灌,不知道行不行

    public class EventMutableLiveData extends LiveData {

    private final AtomicInteger currentVersion = new AtomicInteger(-1);
    
    /**
     * 这个标志主要是是否忽略最新值, observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) 才会生效
     * 比如从页面1 来到页面2 页面2 再回到页面1 页面1默认是会收到页面2 最新的值,不想收到这个值可以设置为true
     */
    private boolean isIgnoreLastObserve;
    
    private final HashMap<Observer<? super T>, ObserverProxy> observerMap = new HashMap<>();
    
    public EventMutableLiveData(boolean isIgnoreLastObserve) {
        super();
        this.isIgnoreLastObserve = isIgnoreLastObserve;
    }
    
    public EventMutableLiveData() {
        super();
    }
    
    @Override
    public void observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) {
        if (!observerMap.containsKey(observer)) {
            observerMap.put(observer, new ObserverProxy(observer, currentVersion.get(), false));
        } else {
            Objects.requireNonNull(observerMap.get(observer)).changeVersion(currentVersion.get());
        }
        super.observe(owner, Objects.requireNonNull(observerMap.get(observer)));
    }
    
    @Override
    public void observeForever(@NonNull @NotNull Observer<? super T> observer) {
        if (!observerMap.containsKey(observer)) {
            observerMap.put(observer, new ObserverProxy(observer, currentVersion.get(), true));
        }
        super.observeForever(Objects.requireNonNull(observerMap.get(observer)));
    }
    
    @Override
    public void setValue(T value) {
        currentVersion.getAndIncrement();
        super.setValue(value);
    }
    
    @Override
    public void postValue(T value) {
        currentVersion.getAndIncrement();
        super.postValue(value);
    }
    
    class ObserverProxy implements Observer<T> {
    
        private final Observer<? super T> observer;
    
        private int mVersion = -1;
    
        private final boolean isForever;
    
        public ObserverProxy(@NotNull Observer<? super T> observer, int mVersion, boolean isForever) {
            this.observer = observer;
            this.mVersion = mVersion;
            this.isForever = isForever;
        }
    
        @Override
        public synchronized void onChanged(T t) {
            if (currentVersion.get() > mVersion) {
                observer.onChanged(t);
                if (isIgnoreLastObserve && !isForever) {
                    Set<Map.Entry<Observer<? super T>, ObserverProxy>> set = observerMap.entrySet();
                    for (Map.Entry<Observer<? super T>, ObserverProxy> item : set) {
                        item.getValue().changeVersion(currentVersion.get());
                    }
                }
            }
        }
    
        private void changeVersion(int newVersion) {
            this.mVersion = newVersion;
        }
    }
    
    @Override
    public void removeObserver(@NonNull @NotNull Observer<? super T> observer) {
        super.removeObserver(observer);
        observerMap.remove(observer);
    }
    
    public void removeAllObserver() {
        Set<Map.Entry<Observer<? super T>, ObserverProxy>> set = observerMap.entrySet();
        for (Map.Entry<Observer<? super T>, ObserverProxy> item : set) {
            super.removeObserver(item.getValue());
        }
        observerMap.clear();
    }
    

    }

    精华 
    opened by RebornWolfman 7
  • 针对使用两个ConcurrentHashMap的问题优化,可参考

    针对使用两个ConcurrentHashMap的问题优化,可参考

    observerStateMap 这个map 感觉没有存在的必要,可以更加精简

    private final ConcurrentHashMap<Observer<? super T>, ChangeablePair<Observer<? super T>, Boolean>> observerProxyMap = new ConcurrentHashMap();

    这是我的想法,一个map就可以搞定 image

    我觉得数据上会更好

    另外针对这一行代码 image 改造成如下: image

    精华 
    opened by liweiGe 1
  • 【 提问须知 】

    【 提问须知 】

    如有 bug,请另外 new 一个 issue ⚠️⚠️⚠️

    本项目开 issue 规范:

    1. 有任何 bug 都欢迎及时开 issue,我看到后予以处理。
    2. 如有使用上的疑问,请先认真阅读 Readme 和源码 sample,在没有找到答案后,另外开 issue。
    3. 如开 issue 是为了发表个人见解,请务必 客观、具体、严谨;严禁草率、乱入、带节奏:

    务必注明观点所对应的场景,并附上完整可复现的代码,

    不然缺乏一致的前提依据来有效交流!

    任何缺乏实证依据和因果逻辑的泛泛而谈,都可能对其他使用者造成困扰。

    在发表个人见解前,请先确保自己认真阅读过源码。这是对自己、对作者、对其他读者最起码的尊重。

    提问须知 
    opened by KunMinX 4
Releases(7.8.0)
Owner
KunMinX
大物始于小- Big Things Have Small Beginnings.
KunMinX
Modern Calendar View Supporting Both Hijri and Gregorian Calendars but in highly dynamic way

KCalendar-View Modern calendar view supporting both Hijri and Gregorian calendar

Ahmed Ibrahim 8 Oct 29, 2022
A lightweight alternative to Android's ViewModels. The easiest way to retain instances in Activities, Fragments or Composables.

A lightweight alternative to Android's ViewModels. The easiest way to retain instances in Activities, Fragments or Composables.

Marcello Galhardo 264 Dec 27, 2022
Disk Usage/Free Utility - a better 'df' alternative

duf Disk Usage/Free Utility (Linux, BSD, macOS & Windows) Features User-friendly, colorful output Adjusts to your terminal's theme & width Sort the re

Christian Muehlhaeuser 10.3k Dec 31, 2022
Android To-Do MVVM Architecture App written in Kotlin.(ViewModel, ROOM, Livedata, Coroutines)

MVVM-To-Do-App A To-Do application written in kotlin using Android Architectural components What's new? Room + Coroutines - Upgraded Room to v2.1. Roo

Naveen T P 77 Dec 8, 2022
Android Library for requesting Permissions with Kotlin Coroutines or AndroidX LiveData

PEKO PErmissions with KOtlin Android Permissions with Kotlin Coroutines or LiveData No more callbacks, builders, listeners or verbose code for request

Marko Devcic 133 Dec 14, 2022
Anime quotes rest api app with mvvm, LiveData, Coroutines, Navigation Component etc

AnimeQuote Anime quotes image rest api app with mvvm, LiveData, Coroutines, Navigation Component, clean achitecture etc An app created during mentorin

Ehma Ugbogo 4 Jun 11, 2021
Sample app that implements MVVM architecture using Kotlin, ViewModel, LiveData, and etc.

TheShard is a project to showcase different architectural approaches to developing Android apps. In its different branches you will find the same app (A movie Listing and detail page) implemented with small differences.

null 17 Aug 19, 2021
Wrapper of FusedLocationProviderClient for Android to support modern usage like LiveData and Flow

FancyLocationProvider Wrapper of FusedLocationProviderClient for Android to support modern usage like LiveData or Flow. Install Add Jitpack repository

Jintin 66 Aug 15, 2022
Marvel Super Heroes | MVVM | Coroutines | DaggerHilt | LiveData

As an iOS developer, I'm learning Android and Kotlin trying to apply best practices, so I've started the same iOS project based on MARVEL, but now for ANDROID!

Míchel Marqués 2 Jun 4, 2022
Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel...

stateflow-flow-sharedflow-livedata Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel... StateFlow,

Raheem 5 Dec 21, 2022
ViewModel ve DataBindig ile LiveData kullanım örneği.

#Android Kotlin ViewModel ve Livedata Görsel Kaynak: https://www.youtube.com/watch?v=suC0OM5gGAA Temiz kod mantığı ve bu mantık doğrultusunda geliştir

Murat YÜKSEKTEPE 0 Nov 20, 2021
ViewModel e LiveData - Android Studio - Kotlin

Unscramble App Starter code for Android Basics codelab - Store the data in a ViewModel Unscramble is a single player game app that displays scrambled

Ademir Oliveira 1 Nov 23, 2021
Realize EventBus with LiveData

LiveDataBus 增強UnPeek-LiveData,將其包裝成觀察者模式的事件總線 Getting started Add it in your root build.gradle at the end of repositories: allprojects { repositor

JaredDoge 2 Oct 25, 2022
A minimal notes application in Jetpack Compose with MVVM architecture. Built with components like DataStore, Coroutines, ViewModel, LiveData, Room, Navigation-Compose, Coil, koin etc.

Paper - A Minimal Notes App A minimal notes application in Jetpack Compose with MVVM architecture. Built with components like DataStore, Coroutines, V

Akshay Sharma 139 Jan 2, 2023
Android Clean Architechture with MVVM, LiveData, Coroutine, Dagger Hilt, Room, DataStore

MovKu An application that displays a list of popular movies and detail Concepts Tech Stack Kotlin -A modern programming language that makes developers

ꦤꦤꦁ​​ꦲꦫꦶꦥ꦳ꦸꦢꦶꦤ꧀ 3 Jul 29, 2022
[Android-Kotlin] MVVM, ViewModel, LiveData, Observer, DataBinding, Repository, Retrofit, Dagger example

SimpleMvvmDaggerKotlin [Android-Kotlin] MVVM, ViewModel, LiveData, Observer, DataBinding, Repository, Retrofit, Dagger example [Image1 : User informat

DONGGEUN JUNG 2 Oct 24, 2022
Android Library to handle multiple Uri's(paths) received through Intents.

?? Handle Path Oz Android library written in Kotlin, but can be used in Java too. Built to handle a single or multiple Uri (paths) received through In

Murillo Comino 57 Dec 14, 2022
Extension functions over Android's callback-based APIs which allows writing them in a sequential way within coroutines or observe multiple callbacks through kotlin flow.

callback-ktx A lightweight Android library that wraps Android's callback-based APIs into suspending extension functions which allow writing them in a

Sagar Viradiya 171 Oct 31, 2022
Annotation proccessor for generate public observers

Anzidcodogen Annotation proccessor for generate public observers Supported annotation PublicSharedFlow, PublicLiveData, InternalSharedFlow, InternalLi

Nazarij 4 Jul 29, 2022
Android library (AAR). Highly configurable, easily extendable deep zoom view for displaying huge images without loss of detail. Perfect for photo galleries, maps, building plans etc.

Subsampling Scale Image View A custom image view for Android, designed for photo galleries and displaying huge images (e.g. maps and building plans) w

null 7.4k Jan 8, 2023