UIのラグを防ぐために、LiveData
オブジェクトによって返された1つのタイプのデータを別の形式バックグラウンドスレッドに変換する必要があります。
私の特定のケースでは、私は:
MyDBRow
オブジェクト(プリミティブlong
sおよびString
sで構成されるPOJO);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
変換を発生させるクリーンな方法は何ですか?
LiveData
は、新しい Observer
インスタンスによって監視できます。Observer
インスタンスは、ソースLiveData
が発行されたときに、必要な変換を実行するバックグラウンドスレッドを準備し、新しい「変換された」LiveData
を介してそれを発行できます。LiveData
は、アクティブなObserver
sがある場合、前述の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;
}
}
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;
}
コルーチンを使用した別の可能な解決策:
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
}