Как я могу выполнить преобразования 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
трансформации происходят:
- где-то вне основного потока, и
- только по мере необходимости (т.е. только когда что-то наблюдает за предполагаемой трансформацией)?
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
, который будет слушать владелец жизненного цикла. ЗаменитьT
s желаемого типа.
Спасибо @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
}