【问题标题】:MediatorLiveData or switchMap transformation with multiple parameters具有多个参数的 MediatorLiveData 或 switchMap 转换
【发布时间】:2018-09-04 17:37:25
【问题描述】:

我在我的 ViewModel 中使用Transformations.switchMap,所以在我的片段中观察到的我的LiveData 集合会对code 参数的变化作出反应。

这很好用:

public class MyViewModel extends AndroidViewModel {

    private final LiveData<DayPrices> dayPrices;
    private final MutableLiveData<String> code = new MutableLiveData<>();
    // private final MutableLiveData<Integer> nbDays = new MutableLiveData<>();
    private final DBManager dbManager;

    public MyViewModel(Application application) {
        super(application);
        dbManager = new DBManager(application.getApplicationContext());
        dayPrices = Transformations.switchMap(
            code,
            value -> dbManager.getDayPriceData(value/*, nbDays*/)
        );
    }

    public LiveData<DayPrices> getDayPrices() {
        return dayPrices;
    }

    public void setCode(String code) {
        this.code.setValue(code);
    }

    /*public void setNbDays(int nbDays) {
        this.nbDays.setValue(nbDays);
    }*/

}

public class MyFragment extends Fragment {

    private MyViewModel myViewModel;

    myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    myViewModel.setCode("SO");
    //myViewModel.setNbDays(30);
    myViewModel.getDayPrices().observe(MyFragment.this, dataList -> {
        // update UI with data from dataList
    });
}

问题

我现在需要另一个参数(nbDays 在上面的代码中注释),以便我的 LiveData 对象对两个参数更改(codenbDays)做出反应。

如何链接转换?

一些阅读将我指向MediatorLiveData,但它并没有解决我的问题(仍然需要使用 2 个参数调用单个 DB 函数,我不需要合并 2 个 liveDatas)。

所以我尝试了这个而不是switchMap,但codenbDays 始终为空。

dayPrices.addSource(
    dbManager.getDayPriceData(code.getValue(), nbDays.getValue),
    apiResponse -> dayPrices.setValue(apiResponse)
);

一种解决方案是将对象作为单个参数传递,我很确定有一个简单的解决方案。

【问题讨论】:

    标签: android observer-pattern android-architecture-components android-livedata android-viewmodel


    【解决方案1】:

    来源:https://plus.google.com/+MichielPijnackerHordijk/posts/QGXF9gRomVi

    要拥有switchMap()的多个触发器,您需要使用自定义MediatorLiveData来观察LiveData对象的组合 -

    class CustomLiveData extends MediatorLiveData<Pair<String, Integer>> {
        public CustomLiveData(LiveData<String> code, LiveData<Integer> nbDays) {
            addSource(code, new Observer<String>() {
                public void onChanged(@Nullable String first) {
                    setValue(Pair.create(first, nbDays.getValue()));
                }
            });
            addSource(nbDays, new Observer<Integer>() {
                public void onChanged(@Nullable Integer second) {
                    setValue(Pair.create(code.getValue(), second));
                }
            });
        }
    }
    

    那么你就可以这样做了-

    CustomLiveData trigger = new CustomLiveData(code, nbDays);
    LiveData<DayPrices> dayPrices = Transformations.switchMap(trigger, 
        value -> dbManager.getDayPriceData(value.first, value.second));
    

    如果您使用 Kotlin 并希望使用泛型:

    class DoubleTrigger<A, B>(a: LiveData<A>, b: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() {
        init {
            addSource(a) { value = it to b.value }
            addSource(b) { value = a.value to it }
        }
    }
    

    然后:

    val dayPrices = Transformations.switchMap(DoubleTrigger(code, nbDays)) {
        dbManager.getDayPriceData(it.first, it.second)
    }
    

    【讨论】:

    • 谢谢,非常有帮助!但是在这种情况下 switchMap 连续两次初始化,在这种情况下是否可以跳过第一个源?
    • @aleksandrbel 你的意思是switchMap可以使用codenbDays的单独设置器触发两次?然后是的,这可能导致观察者收到两个背靠背的值。如果您不希望这种行为,最好在 MutableLiveData 中仅使用单个 Pair 对象,该对象结合了以下 OP 的答案中的两个值。
    【解决方案2】:

    @jL4 提出的自定义MediatorLiveData 效果很好,可能是解决方案。

    我只是想分享一下我认为最简单的解决方案,即使用内部类来表示组合的过滤器值:

    public class MyViewModel extends AndroidViewModel {
    
        private final LiveData<DayPrices> dayPrices;
        private final DBManager dbManager;
        private final MutableLiveData<DayPriceFilter> dayPriceFilter;
    
        public MyViewModel(Application application) {
            super(application);
            dbManager = new DBManager(application.getApplicationContext());
            dayPriceFilter = new MutableLiveData<>();
            dayPrices = Transformations.switchMap(dayPriceFilter, input -> dbManager.getDayPriceData(input.code, input.nbDays));
        }
    
        public LiveData<DayPrices> getDayPrices() {
            return dayPrices;
        }
    
        public void setDayPriceFilter(String code, int nbDays) {
            DayPriceFilter update = new DayPriceFilter(code, nbDays);
            if (Objects.equals(dayPriceFilter.getValue(), update)) {
                return;
            }
            dayPriceFilter.setValue(update);
        }
    
        static class DayPriceFilter {
            final String code;
            final int nbDays;
    
            DayPriceFilter(String code, int nbDays) {
                this.code = code == null ? null : code.trim();
                this.nbDays = nbDays;
            }
        }
    
    }
    

    然后在活动/片段中:

    public class MyFragment extends Fragment {
    
        private MyViewModel myViewModel;
    
        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        myViewModel.setDayPriceFilter("SO", 365);
        myViewModel.getDayPrices().observe(MyFragment.this, dataList -> {
            // update UI with data from dataList
        });
    }
    

    【讨论】:

    • 您可能希望在 DayPriceFilter 中实现“等于”。
    【解决方案3】:

    jL4 的答案的简化,(以及在 Kotlin 中,以防它帮助任何人)...无需为此创建自定义类:

    class YourViewModel: ViewModel() {
    
        val firstLiveData: LiveData<String> // or whatever type
        val secondLiveData: LiveData<Int> // or whatever
    
        // the Pair values are nullable as getting "liveData.value" can be null
        val combinedValues = MediatorLiveData<Pair<String?, Int?>>().apply {
            addSource(firstLiveData) { 
               value = Pair(it, secondLiveData.value)
            }
            addSource(secondLiveData) { 
               value = Pair(firstLiveData.value, it)
            }
        }
    
        val results = Transformations.switchMap(combinedValues) { pair ->
          val firstValue = pair.first
          val secondValue = pair.second
          if (firstValue != null && secondValue != null) {
             yourDataSource.yourLiveDataCall(firstValue, secondValue)
          } else null
        }
    
    }
    

    说明

    firstLiveDatasecondLiveData 中的任何更新都会更新 combinedValues 的值,并将这两个值作为一对发出(感谢 jL4)。

    调用liveData.value 可以为null,因此该解决方案使Pair 中的值可以为null,以避免出现Null Pointer Exception。

    所以对于实际的结果/数据源调用,switch map在combinedValues实时数据上,从Pair中提取2个值并进行空检查,所以可以肯定通过非将 null 值添加到您的数据源。

    【讨论】:

    • 这是一个非常好的解决方案。反正有N个来源扩展它吗?我有一个案例来源编号未知..!
    【解决方案4】:

    我遇到了类似的问题。有两种方法可以解决这个问题:

    1. 要么使用MediatorLiveData
    2. 使用 RxJava,因为它有各种操作符来处理这种复杂的事情

    如果您不了解 RxJava,那么我建议您编写自定义的 MediatorLiveData 类。 要了解如何编写自定义 MediatorLiveData 类,请查看以下示例: https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b

    【讨论】:

    • 如何进行需要 4 个 LiveData(例如 userId、emial、apiKey 等)的网络调用。Transformation.switchMap 只接受一个 LiveData 参数请给出一些建议。太赫兹
    • 我遇到了类似的情况,我使用了MediatorLiveData。我会推荐它,看看它是否适合您的用例,否则您可以使用 RxJava,然后将其转换为 LiveData
    • 如果你不介意,你能分享一些代码sn-p吗?
    • @RichardLeMesurier 它不断变得无效,因为存储库中不断发生变化。因此,现在我添加了一个指向我的要点的链接,所以它不会中断
    • 令人沮丧,是的。我还刚刚发布了一个答案的图片链接,我知道它很快也会死掉......
    猜你喜欢
    • 2020-11-06
    • 2020-05-25
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    • 2021-10-29
    相关资源
    最近更新 更多