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 long
s et String
s primitifs);LiveData<List<MyDBRow>>
; etMyRichObject
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
:
LiveData
peut être surveillé par une nouvelle instance Observer
.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é".LiveData
transformé peut attacher le Observer
susmentionné à la source LiveData
lorsqu'il a des Observer
s 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;
}
}
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;
}
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
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)
}
}
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
}