具有多个参数的 MediatorLiveData 或 switchMap 转换
MediatorLiveData or switchMap transformation with multiple parameters
我在我的 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
对象对两个参数变化(code
和 nbDays
)做出反应.
如何链式转换?
一些阅读指向我 MediatorLiveData,但它没有解决我的问题(仍然需要使用 2 个参数调用单个 DB 函数,我不需要合并 2 个 liveDatas)。
所以我尝试了这个而不是 switchMap
但是 code
和 nbDays
总是空的。
dayPrices.addSource(
dbManager.getDayPriceData(code.getValue(), nbDays.getValue),
apiResponse -> dayPrices.setValue(apiResponse)
);
一个解决方案是将一个对象作为单个参数传递,我很确定有一个简单的解决方案。
我遇到了类似的问题。有两种方法可以解决这个问题:
- 或者使用
MediatorLiveData
- 使用 RxJava,因为它有各种运算符来做这种复杂的事情
如果您不了解 RxJava,那么我建议您编写自定义 MediatorLiveData
class。
要了解如何编写自定义 MediatorLiveData
class,请查看此示例:
https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b
来源: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)
}
@jL4 提出的自定义 MediatorLiveData
效果很好,可能是解决方案。
我只是想分享我认为最简单的解决方案,即使用内部 class 来表示组合过滤器值:
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;
}
}
}
然后在 activity/fragment 中:
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
});
}
jL4 答案的简化,(以及在 Kotlin 中,以防它对任何人有帮助)...无需为此创建自定义 class:
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
}
}
说明
firstLiveData
或 secondLiveData
中的任何更新都会更新 combinedValues
的值,并将这两个值成对发出(感谢 jL4)。
调用liveData.value
可以为空,所以这个解决方案使Pair中的值可以为空以避免空指针异常。
所以对于实际的 results/datasource 调用,开关映射在 combinedValues
实时数据上,并且从 Pair
中提取 2 个值并执行空检查,所以您可以确定将非空值传递给数据源。
我在我的 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
对象对两个参数变化(code
和 nbDays
)做出反应.
如何链式转换?
一些阅读指向我 MediatorLiveData,但它没有解决我的问题(仍然需要使用 2 个参数调用单个 DB 函数,我不需要合并 2 个 liveDatas)。
所以我尝试了这个而不是 switchMap
但是 code
和 nbDays
总是空的。
dayPrices.addSource(
dbManager.getDayPriceData(code.getValue(), nbDays.getValue),
apiResponse -> dayPrices.setValue(apiResponse)
);
一个解决方案是将一个对象作为单个参数传递,我很确定有一个简单的解决方案。
我遇到了类似的问题。有两种方法可以解决这个问题:
- 或者使用
MediatorLiveData
- 使用 RxJava,因为它有各种运算符来做这种复杂的事情
如果您不了解 RxJava,那么我建议您编写自定义 MediatorLiveData
class。
要了解如何编写自定义 MediatorLiveData
class,请查看此示例:
https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b
来源: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)
}
@jL4 提出的自定义 MediatorLiveData
效果很好,可能是解决方案。
我只是想分享我认为最简单的解决方案,即使用内部 class 来表示组合过滤器值:
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;
}
}
}
然后在 activity/fragment 中:
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
});
}
jL4 答案的简化,(以及在 Kotlin 中,以防它对任何人有帮助)...无需为此创建自定义 class:
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
}
}
说明
firstLiveData
或 secondLiveData
中的任何更新都会更新 combinedValues
的值,并将这两个值成对发出(感谢 jL4)。
调用liveData.value
可以为空,所以这个解决方案使Pair中的值可以为空以避免空指针异常。
所以对于实际的 results/datasource 调用,开关映射在 combinedValues
实时数据上,并且从 Pair
中提取 2 个值并执行空检查,所以您可以确定将非空值传递给数据源。