AAC学习笔记LiveData(三)——Transformations

前面叨叨了几个LiveData的类,这一篇探讨一个在GithubBrowserSample里实际使用的场景。


Transformations

类中提供了用于功能组合和委托 LiveData实例的方法。
这些转换是惰性计算的,并且只在观察到返回的LiveData时运行。
这里面使用了Function

public interface Function<I, O> {
    /**
     * @param input the input
     * @return the function result.
     */
    O apply(I input);
}

就是将输入经过apply处理后转变为输出。
个人认为Kotlin的函数字面量表达这类接口更简单、易读。

map

这个方法我应该叫它映射吧。

    public static <X, Y> LiveData<Y> map(
            @NonNull LiveData<X> source,            
            @NonNull final Function<X, Y> mapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            @Override
            public void onChanged(@Nullable X x) {
                //调用onChanged时:用apply()处理后的返回值,更新result
                result.setValue(mapFunction.apply(x));   
            }
        });
        //返回一个LiveData
        return result;
    }

这个用法后面会聊,本篇重点聊下一个方法。

switchMap

暂且叫他切换映射吧。

    public static <X, Y> LiveData<Y> switchMap(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction) { 
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        //给result添加一个源(source),当变化时执行Observer.onChanged()
        result.addSource(source, new Observer<X>() {
            LiveData<Y> mSource;

            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.apply(x);
                //被使用过,已经存在,返回
                //注意第一次执行时,此处是执行不到的,因为mSource为null
                //这里的mSource可是一个新的哦,与上面的source一点关系都没有
                //这里的mSource与上面的source是一同加入resule.mSources里地位相等的两个源而已
                if (mSource == newLiveData) {return;} 
                if (mSource != null) { result.removeSource(mSource); }//存在源,则将其移除
                mSource = newLiveData;
                //这里是当switchMapFunction.apply(x)返回后,返回的必须是LiveData
                //将返回的LiveData作为result的source注册到result里
                //第二个源的改变会传递给result
                if (mSource != null) {
                    result.addSource(mSource,(Observer)(Y)-> { result.setValue(y);}
                    });
                }
            }
        });
        
        return result;
    }

switchMap()有两个参数,会产生两个源(source),然后返回result(MediatorLiveData类型):

  • 第一个源:传入的第一个LiveData类型参数。
  • 第二个源:当第一参数发生改变时执行onChanged()。通过switchMapFunction.apply(x)返回并转换一个LiveData,这个返回值将作为result的第二个源被加入。
  • 当第二个源发生变化时会将变化结果传递给result。

所以:

  • switchMap()返回值存在两个源。第一个源(第一参数)改变会导致第二源的改变(并不是传递结果!),第二源的(第二参数返回的值)改变会导致result的改变(传递结果
  • 每次第一源的内容发生改变的时候,都会对第二源执行removeSource()并再addSource()。也就是进行替换

实际使用

这里引述GithubBrowserSample里一个使用的地方,来说明一下。

定义

首先看SearchViewModel开始部分定义:

class SearchViewModel @Inject constructor(repoRepository: RepoRepository) : ViewModel() {
    private val query = MutableLiveData<String>()
    val results: LiveData<Resource<List<Repo>>> = Transformations
        .switchMap(query){ search ->
            if (search.isNullOrBlank()) {
                AbsentLiveData.create()
            } else {
                repoRepository.search(search)
            }
        }

由于示例使用了依赖注入,所以一开始就会有一个results(当然是加载了MainActivity以后)。这时results只里有一个源,就是开始定义的query。

这里定义的results是如何被使用的呢,我们来看SearchFragment.initRecyclerView():

        searchViewModel.results.observe(this, Observer { result ->
            binding.searchResource = result
            binding.resultCount = result?.data?.size ?: 0
            adapter.submitList(result?.data)
        })

增加了观察者,也就是当results发生改变回去更新页面。

参数

在SearchViewModel 里的Transformations.switchMap()
第一个参数: query,一个String的MutableLiveData。
query是用来接收一个字符串,作为搜索的关键词在Github上进行搜索,然后将返回值填充到页面。
第二个参数: 写法是kotlin的尾随式Lambda表达式,我们可以看到最终返回值也是一个LiveData:

    fun <T>  AbsentLiveData.create(): LiveData<T> {return AbsentLiveData()}
        
    fun repoRepository.search(query: String): LiveData<Resource<List<Repo>>> {
        return object : NetworkBoundResource<List<Repo>, RepoSearchResponse>(appExecutors) 
        {...}.asLiveData()
    }

不要纠结上面代码,只是说明了返回一个LiveData而已。

串起来说

当我们输入一个关键字后执行query.value = input
这时第一个参数发生变化,导致执行了results第一个源的onChanged()
这个results.onChanged()定义在Transformations.switchMap()

            public void onChanged(@Nullable X x) {                
                LiveData<Y> newLiveData = switchMapFunction.apply(x);
                ... }

这一句执行的是下面代码大括号里的部分,也就是在SearchViewModel里定义的部分:

 val results: LiveData<Resource<List<Repo>>> = Transformations
        .switchMap(query){ search ->
            if (search.isNullOrBlank()) {
                AbsentLiveData.create()
            } else {
                repoRepository.search(search)
            }

results.onChanged()定义在Transformations.switchMap()的完整代码:

            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }

由于我比较笨所以这段代码我粘了两遍,这里会发现真正用于更新SearchViewModel.results的是第二个参数返回的LiveData。
为什么有三个if,在文档里有这样的描述:

The returned LiveData delegates to the most recent LiveData created by calling switchMapFunction with the most recent value set to source, without changing the reference. In this way, switchMapFunction can change the ‘backing’ LiveData transparently to any observer registered to the LiveData returned by switchMap().
返回的LiveData委托给最近通过调用switchMapFunction创建的LiveData,如果近期使用过并且相同,不改变引用。通过这种方式,switchMapFunction可以透明地改变被观察者。切换被观察者LiveData时,不会将之前的值输出。

Note that when the backing LiveData is switched, no further values from the older LiveData will be set to the output LiveData.
注意,当切换了后端的LiveData时,不会将旧LiveData的值输出。

其原理是,作为第二个源的LiveDate(由第一个源发生变化而产生的)当被用过一次后,如果第二次使用时已经被更新了,那么将之前使用过的移除并将新值新作为源加入。

总结一下

通过示例的应用,应该比较清晰的理解Transformations的原理。
query的改变导致返回值的改变,进而改变results。而results将执行更新页面的操作。
switchMap()第二个参数的返回值是可以根据query的改变而切换的(切换的新对象)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值