Как я могу выполнить преобразования LiveData в фоновом потоке?

У меня есть необходимость преобразовать один тип данных, возвращаемых LiveData объект, в другую форму в фоновом потоке, чтобы предотвратить отставание пользовательского интерфейса.

В моем конкретном случае у меня есть:

  • MyDBRow объекты (POJO, состоящие из примитива long с и String с);
  • экземпляр комнаты DAO, испускающий их через LiveData<List<MyDBRow>>; а также
  • пользовательский интерфейс, ожидающий богаче MyRichObject объекты (POJO с примитивами, накачанными, например, в объекты даты / времени)

поэтому мне нужно преобразовать мой LiveData<List<MyDBRow>> в LiveData<List<MyRichObject>>, но не в потоке пользовательского интерфейса.

Transformations.map(LiveData<X>, Function<X, Y>) Метод выполняет это необходимое преобразование, но я не могу использовать это, потому что он выполняет преобразование в основном потоке:

Применяет данную функцию в главном потоке к каждому значению, выдаваемому source LiveData и возвращает LiveData, которая выдает результирующие значения.

Данная функция func будет выполняться в главном потоке.

Что такое чистый способ сделать LiveData трансформации происходят:

  1. где-то вне основного потока, и
  2. только по мере необходимости (т.е. только когда что-то наблюдает за предполагаемой трансформацией)?

7 ответов

Решение
  • Оригинал, "источник" LiveData может контролироваться новым Observer пример.
  • это Observer случай, когда источник LiveData испускается, может подготовить фоновый поток для выполнения необходимого преобразования, а затем выдать его через новый, "преобразованный" LiveData,
  • Преобразованный LiveData можно приложить вышеупомянутое Observer к источнику LiveData когда он активен Observer s, и отсоедините их, когда это не так, гарантируя, что источник 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>
{
    private Observer<Source> observer = new Observer<Source>() {
        @Override public void onChanged(@Nullable final Source source) {
            AsyncTask.execute(new Runnable() {
                @Override public void run() {
                    postValue(getTransformed(source));
                }
            });
        }
    };

    @Override protected void onActive()   { getSource().observeForever(observer); }
    @Override protected void onInactive() { getSource().removeObserver(observer); }

    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;
    }
}

Это может быть eaiser сделать с помощью MediatorLiveData, Transformations.map() реализуется с MediatorLiveData под капотом.

@MainThread
public static <X, Y> LiveData<Y> mapAsync(
  @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 final X x) {
      AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
          result.postValue(mapFunction.apply(x));
        }
      });
    }
  });
  return result;
}

Слушайте 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, который будет слушать владелец жизненного цикла. ЗаменитьTs желаемого типа.

Спасибо @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
}

Другое возможное решение с сопрограммами:

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
    }

}

Надеюсь, это поможет

Решение с сопрограммами:

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)
    }
}

Как насчет этого:

@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
}