我需要将LiveData
对象返回的一种数据转换为后台线程上的另一种形式,以防止UI延迟。
在我的具体案例中,我有:
MyDBRow
个对象(由原始long
和String
组成的POJO); LiveData<List<MyDBRow>>
发出这些内容;和MyRichObject
对象的用户界面(原始广告被夸大为date/time objects的POJO)所以我需要将LiveData<List<MyDBRow>>
转换为LiveData<List<MyRichObject>>
,但不要转换为UI线程。
Transformations.map(LiveData<X>, Function<X, Y>)
方法执行此操作需要转换,但我无法使用此方法,因为在主线程上执行转换:
将主线程上的给定函数应用于
source
LiveData发出的每个值,并返回生成结果值的LiveData。给定的函数
func
将在主线程上执行。
进行LiveData
转换的干净方法是什么:
答案 0 :(得分:15)
LiveData
可由新的Observer
个实例监控。Observer
时,此LiveData
实例可以准备后台线程来执行所需的转换,然后通过新的“转换后的”LiveData
发出它。LiveData
可以将前面提到的Observer
附加到源LiveData
,当它有活跃Observer
时,并在它没有时将其分离,确保只在必要时才会发现来源LiveData
。问题提供了一个示例来源LiveData<List<MyDBRow>>
,需要转换后的LiveData<List<MyRichObject>>
。合并后的LiveData
和Observer
组合可能如下所示:
class MyRichObjectLiveData
extends LiveData<List<MyRichObject>>
implements Observer<List<MyDBRow>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
// only watch the source LiveData when something is observing this
// transformed LiveData
@Override protected void onActive() { sourceLiveData.observeForever(this); }
@Override protected void onInactive() { sourceLiveData.removeObserver(this); }
// receive source LiveData emission
@Override public void onChanged(@Nullable List<MyDBRow> dbRows) {
// set up a background thread to complete the transformation
AsyncTask.execute(new Runnable() {
@Override public void run() {
assert dbRows != null;
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
// use LiveData method postValue (rather than setValue) on
// background threads
postValue(myRichObjects);
}
});
}
}
如果需要多次这样的转换,上面的逻辑可以像这样通用:
abstract class TransformedLiveData<Source, Transformed>
extends LiveData<Transformed>
implements Observer<Source>
{
@Override protected void onActive() { getSource().observeForever(this); }
@Override protected void onInactive() { getSource().removeObserver(this); }
@Override public void onChanged(@Nullable Source source) {
AsyncTask.execute(new Runnable() {
@Override public void run() {
postValue(getTransformed(source));
}
});
}
protected abstract LiveData<Source> getSource();
protected abstract Transformed getTransformed(Source source);
}
并且问题给出的示例的子类看起来像这样:
class MyRichObjectLiveData
extends TransformedLiveData<List<MyDBRow>, List<MyRichObject>>
{
@NonNull private LiveData<List<MyDBRow>> sourceLiveData;
MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {
this.sourceLiveData = sourceLiveData;
}
@Override protected LiveData<List<MyDBRow>> getSource() {
return sourceLiveData;
}
@Override protected List<MyRichObject> getTransformed(List<MyDBRow> myDBRows) {
List<MyRichObject> myRichObjects = new LinkedList<>();
for (MyDBRow myDBRow : myDBRows) {
myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());
}
return myRichObjects;
}
}
答案 1 :(得分:6)
使用from flask.ext.xxx import xxx
可能更容易。 MediatorLiveData
与Transformations.map()
一起使用。
MediatorLiveData
答案 2 :(得分:3)
听一个MediatorLiveData<T>
,它会收听另外两个LiveData<T>
。
例如:
val exposed: LiveData<List<T>> = MediatorLiveData<List<T>>().apply {
addSource(aLiveDataToMap) { doWorkOnAnotherThread(it) }
addSource(aMutableLiveData) { value = it }
}
private fun doWorkOnAnotherThread(t: T) {
runWorkOnAnotherThread {
val t2 = /* ... */
aMutableLiveData.postValue(t2)
}
}
只要aLiveDataToMap
发生更改,它就会触发doWorkOnAnotherThread()
,这将设置aMutableLiveData
的值,最后将其设置为exposed
的值,生命周期所有者将使用该值在听。将T
替换为所需的类型。
答案 3 :(得分:2)
协程的另一种可能的解决方案:
object BackgroundTransformations {
fun <X, Y> map(
source: LiveData<X>,
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, Observer<X> { x ->
if (x == null) return@Observer
CoroutineScope(Dispatchers.Default).launch {
result.postValue(mapFunction(x))
}
})
return result
}
fun <X, Y> switchMap(
source: LiveData<X>,
switchMapFunction: (X) -> LiveData<Y>
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source, object : Observer<X> {
var mSource: LiveData<Y>? = null
override fun onChanged(x: X) {
if (x == null) return
CoroutineScope(Dispatchers.Default).launch {
val newLiveData = switchMapFunction(x)
if (mSource == newLiveData) {
return@launch
}
if (mSource != null) {
result.removeSource(mSource!!)
}
mSource = newLiveData
if (mSource != null) {
result.addSource(mSource!!) { y ->
result.setValue(y)
}
}
}
}
})
return result
}
}
希望有帮助
答案 4 :(得分:1)
感谢@jaychang0917
科特林表单:
@MainThread
fun <X, Y> mapAsync(source: LiveData<X>, mapFunction: androidx.arch.core.util.Function<X, Y>): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(source) { x -> AsyncTask.execute { result.postValue(mapFunction.apply(x)) } }
return result
}
答案 5 :(得分:0)
带有协程的解决方案:
class RichLiveData(val rows: LiveData<List<MyDBRow>>) : LiveData<List<MyRichObject>>(),
CoroutineScope by CoroutineScope(Dispatchers.Default) {
private val observer = Observer<List<MyDBRow>> { rows ->
launch {
postValue(/*computationally expensive stuff which returns a List<MyRichObject>*/)
}
}
override fun onActive() {
rows.observeForever(observer)
}
override fun onInactive() {
rows.removeObserver(observer)
}
}
答案 6 :(得分:0)
这样怎么样:
@Query("SELECT * FROM " + PeriodicElement.TABLE_NAME)
abstract fun getAll(): LiveData<List<PeriodicElement>>
fun getAllElements(): LiveData<HashMap<String, PeriodicElement>> {
return Transformations.switchMap(getAll(), ::transform)
}
private fun transform(list: List<PeriodicElement>): LiveData<HashMap<String, PeriodicElement>> {
val map = HashMap<String, PeriodicElement>()
val liveData = MutableLiveData(map)
AsyncTask.execute {
for (p in list) {
map[p.symbol] = p
if (!liveData.hasObservers()) {
//prevent memory leak
break
}
}
liveData.postValue(map)
}
return liveData
}