android 具有多个参数的MediatorLiveData或switchMap转换

wz8daaqr  于 2022-12-16  发布在  Android
关注(0)|答案(5)|浏览(116)

我在ViewModel中使用Transformations.switchMap,因此在片段中观察到的LiveData集合会对code参数的更改做出React。
这非常有效:

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)都做出React。
如何链接转换?
一些阅读指向我MediatorLiveData,但它并没有解决我的问题(仍然需要调用带有2个参数的单个DB函数,我不需要合并2个liveDatas)。
所以我尝试用这个代替switchMap,但是codenbDays总是空的。

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

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

db2dz4w8

db2dz4w81#

来源: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)
}
bgibtngc

bgibtngc2#

@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
    });
}
mwngjboj

mwngjboj3#

jL 4答案的简化,(在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的值,并将这两个值作为一对发出(这要感谢jL 4)。
调用liveData.value可以为空,因此此解决方案使Pair中的值可为空,以避免空指针异常。
因此,对于实际的results/datasource调用,开关Map位于combinedValues活动数据上,从Pair中提取2个值并执行空值检查,因此可以确保将非空值传递到数据源。

bejyjqdl

bejyjqdl4#

我也遇到过类似的问题。有两种方法可以解决这个问题:
1.要么使用MediatorLiveData
1.使用RxJava,因为它有各种各样的操作符来完成这种复杂的事情
如果你不知道RxJava,那么我建议你编写自定义的MediatorLiveData类。要学习如何编写自定义的MediatorLiveData类,请查看以下示例:https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b

bbmckpt7

bbmckpt75#

我使用以下类来转换许多不同类型的实时数据

class MultiMapLiveData<T>(
    private val liveDataSources: Array<LiveData<*>>,
    private val waitFirstValues: Boolean = true,
    private val transform: (signalledLiveData: LiveData<*>) -> T
): LiveData<T>() {
    private val mObservers = ArrayList<Observer<Any>>()
    private var mInitializedSources = mutableSetOf<LiveData<*>>()

    override fun onActive() {
        super.onActive()

        if (mObservers.isNotEmpty()) throw InternalError(REACTIVATION_ERROR_MESSAGE)
        if (mInitializedSources.isNotEmpty()) throw InternalError(REACTIVATION_ERROR_MESSAGE)

        for (t in liveDataSources.indices) {
            val liveDataSource = liveDataSources[t]
            val observer = Observer<Any> {
                if (waitFirstValues) {
                    if (mInitializedSources.size < liveDataSources.size) {
                        mInitializedSources.add(liveDataSource)
                    }
                    if (mInitializedSources.size == liveDataSources.size) {
                        value = transform(liveDataSource)
                    }
                } else {
                    value = transform(liveDataSource)
                }
            }
            liveDataSource.observeForever(observer)
            mObservers.add(observer)
        }
    }

    override fun onInactive() {
        super.onInactive()
        for (t in liveDataSources.indices) {
            val liveDataSource = liveDataSources[t]
            val observer = mObservers[t]
            liveDataSource.removeObserver(observer)
        }
        mObservers.clear()
        mInitializedSources.clear()
    }

    companion object {
        private const val REACTIVATION_ERROR_MESSAGE = "Reactivation of active LiveData"
    }
}

class MyTransformations {
    companion object {
        fun <T> multiMap(
            liveDataSources: Array<LiveData<*>>,
            waitFirstValues: Boolean = true,
            transform: (signalledLiveData: LiveData<*>) -> T
        ): LiveData<T> {
            return MultiMapLiveData(liveDataSources, waitFirstValues, transform)
        }

        fun <T> multiSwitch(
            liveDataSources: Array<LiveData<*>>,
            waitFirstValues: Boolean = true,
            transform: (signalledLiveData: LiveData<*>) -> LiveData<T>
        ): LiveData<T> {
            return Transformations.switchMap(
                multiMap(liveDataSources, waitFirstValues) {
                    transform(it)
                }) {
                    it
                }
        }
    }
}

用法:请注意,工作逻辑略有不同。导致更新的LiveData(signalledLiveData)作为参数传递给转换侦听器,而不是所有LiveData的值。您可以通过value属性以常规方式自己获取当前LiveData值。
示例:

class SequenceLiveData(
    scope: CoroutineScope,
    start: Int,
    step: Int,
    times: Int
): LiveData<Int>(start) {
    private var current = start
    init {
        scope.launch {
            repeat (times) {
                value = current
                current += step
                delay(1000)
            }
        }
    }
}


suspend fun testMultiMap(lifecycleOwner: LifecycleOwner, scope: CoroutineScope) {
    val liveS = MutableLiveData<String>("aaa")
    val liveI = MutableLiveData<Int>()
    val liveB = MutableLiveData<Boolean>()

    val multiLiveWait: LiveData<String> = MyTransformations.multiMap(arrayOf(liveS, liveI, liveB)) {
        when (it) {
            liveS -> log("liveS changed")
            liveI -> log("liveI changed")
            liveB -> log("liveB changed")
        }
        "multiLiveWait: S = ${liveS.value}, I = ${liveI.value}, B = ${liveB.value}"
    }

    val multiLiveNoWait: LiveData<String> = MyTransformations.multiMap(arrayOf(liveS, liveI, liveB), false) {
        when (it) {
            liveS -> log("liveS changed")
            liveI -> log("liveI changed")
            liveB -> log("liveB changed")
        }
        "multiLiveNoWait: S = ${liveS.value}, I = ${liveI.value}, B = ${liveB.value}"
    }

    multiLiveWait.observe(lifecycleOwner) {
        log(it)
    }

    multiLiveNoWait.observe(lifecycleOwner) {
        log(it)
    }

    scope.launch {
        delay(1000)
        liveS.value = "bbb"
        delay(1000)
        liveI.value = 2222
        delay(1000)
        liveB.value = true          // ***
        delay(1000)
        liveI.value = 3333

        //  multiLiveWait generates:
        //
        //           <-- waits until all sources get first values (***)
        //
        //      liveB changed: S = bbb, I = 2222, B = true
        //      liveI changed: S = bbb, I = 3333, B = true

        //  multiLiveNoWait generates:
        //      liveS changed: S = aaa, I = null, B = null
        //      liveS changed: S = bbb, I = null, B = null
        //      liveI changed: S = bbb, I = 2222, B = null
        //      liveB changed: S = bbb, I = 2222, B = true      <-- ***
        //      liveI changed: S = bbb, I = 3333, B = true

    }
}

suspend fun testMultiMapSwitch(lifecycleOwner: LifecycleOwner, scope: CoroutineScope) {
    scope.launch {
        val start1 = MutableLiveData(0)
        val step1 = MutableLiveData(1)
        val multiLiveData = MyTransformations.multiSwitch(arrayOf(start1, step1)) {
            SequenceLiveData(scope, start1.value!!, step1.value!!, 5)
        }

        multiLiveData.observe(lifecycleOwner) {
            log("$it")
        }
        delay(7000)

        start1.value = 100
        step1.value = 2
        delay(7000)

        start1.value = 200
        step1.value = 3
        delay(7000)

        // generates:
        //      0
        //      1
        //      2
        //      3
        //      4
        //      100     <-- start.value = 100
        //      100     <-- step.value = 2
        //      102
        //      104
        //      106
        //      108
        //      200     <-- start.value = 200
        //      200     <-- step.value = 3
        //      203
        //      206
        //      209
        //      212

    }
}

相关问题