web-dev-qa-db-ja.com

バックグラウンドスレッドでLiveData変換を実行するにはどうすればよいですか?

UIのラグを防ぐために、LiveDataオブジェクトによって返された1つのタイプのデータを別の形式バックグラウンドスレッドに変換する必要があります。

私の特定のケースでは、私は:

  • MyDBRowオブジェクト(プリミティブlongsおよびStringsで構成されるPOJO);
  • a Room[〜#〜] dao [〜#〜] _LiveData<List<MyDBRow>>_を介してこれらを出力するインスタンスそして
  • より豊富なMyRichObjectオブジェクトを期待するUI(たとえば、プリミティブが膨らんだPOJO date/time objects

そのため、_LiveData<List<MyDBRow>>_を_LiveData<List<MyRichObject>>_に変換する必要がありますが、-Iスレッドにはありませんです。

Transformations.map(LiveData<X>, Function<X, Y>) メソッドはこの必要な変換を行いますが、これはメインスレッドで変換を実行するであるため使用できません。

メインスレッドの指定された関数をsource LiveDataによって発行された各値に適用し、結果の値を発行するLiveDataを返します。

与えられた関数funcはメインスレッドで実行されます。

LiveData変換を発生させるクリーンな方法は何ですか?

  1. メインスレッドのどこか、そして
  2. 必要に応じてのみ(つまり、意図された変換を何かが観察しているときのみ)?
17
Alex Peters
  • オリジナルの「ソース」 LiveData は、新しい Observer インスタンスによって監視できます。
  • このObserverインスタンスは、ソースLiveDataが発行されたときに、必要な変換を実行するバックグラウンドスレッドを準備し、新しい「変換された」LiveDataを介してそれを発行できます。
  • 変換されたLiveDataは、アクティブなObserversがある場合、前述のLiveDataをソースObserverにアタッチし、アクティブでない場合はそれらをデタッチして、ソースLiveDataは、必要な場合にのみ監視されます。

質問はサンプルソースLiveData<List<MyDBRow>>を示し、変換されたLiveData<List<MyRichObject>>が必要です。変換されたLiveDataObserverを組み合わせると、次のようになります。

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;
    }
}
18
Alex Peters

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

コルーチンを使用した別の可能な解決策:

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
    }

}

それが役に立てば幸い

0
D.Roters

コルーチンを使用したソリューション:

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

このようにどうですか:

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