web-dev-qa-db-fra.com

Comment puis-je effectuer des transformations LiveData sur un thread d'arrière-plan?

J'ai besoin de transformer un type de données, retourné par un objet LiveData, en une autre forme sur un thread d'arrière-plan pour empêcher l'interface utilisateur décalage.

Dans mon cas particulier, j'ai:

  • MyDBRow objets (POJO composés de longs et Strings primitifs);
  • une Room DAO instance les émettant via un LiveData<List<MyDBRow>>; et
  • une interface utilisateur attendant des objets MyRichObject plus riches (POJO avec les primitives gonflées par exemple objets date/heure )

j'ai donc besoin de transformer mon LiveData<List<MyDBRow>> en un LiveData<List<MyRichObject>>, mais pas sur le thread d'interface utilisateur .

La méthode Transformations.map(LiveData<X>, Function<X, Y>) effectue cette transformation nécessaire, mais je ne peux pas l'utiliser car elle exécute la transformation sur le thread principal :

Applique la fonction donnée sur le thread principal à chaque valeur émise par source LiveData et retourne LiveData, qui émet les valeurs résultantes.

La fonction donnée func sera exécutée sur le thread principal.

Quelle est une façon propre de faire des transformations LiveData:

  1. quelque part hors du fil principal, et
  2. uniquement au besoin (c'est-à-dire uniquement lorsque quelque chose observe la transformation prévue)?
17
Alex Peters
  • L'original, "source" LiveData peut être surveillé par une nouvelle instance Observer .
  • Cette instance Observer, lorsque la source LiveData est émise, peut préparer un thread d'arrière-plan pour effectuer la transformation nécessaire, puis l'émettre via un nouveau LiveData "transformé".
  • Le LiveData transformé peut attacher le Observer susmentionné à la source LiveData lorsqu'il a des Observers actifs, et les détacher lorsqu'il ne l'est pas, en s'assurant que le source LiveData n'est observée qu'en cas de besoin.

La question donne un exemple de source LiveData<List<MyDBRow>> et a besoin d'une transformation LiveData<List<MyRichObject>>. Un LiveData et Observer transformé transformé pourrait ressembler à ceci:

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

Si plusieurs de ces transformations sont nécessaires, la logique ci-dessus pourrait être rendue générique comme ceci:

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

et la sous-classe de l'exemple donné par la question pourrait ressembler à ceci:

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

Il peut être plus facile de le faire en utilisant MediatorLiveData. Transformations.map() est implémentée avec MediatorLiveData sous le capot.

@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

Une autre solution possible avec les coroutines:

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
    }

}

J'espère que ça aide

0
D.Roters

Une solution avec coroutines:

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

Et comme ça:

@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
Goran Horia Mihail