将 Paging Library 3 与来自 Java 的 LiveData 结合使用

Using Paging Library 3 with LiveData from Java

我想将 Paging Library 3 与来自 Java 的 LiveData 结合使用。 documentation 解释了如何使用 Guava Futures, RxJava Singles and Kotlin Coroutines 但没有说明如何与 Java 中的 LiveData 一起使用。我大概可以 各种PagingSource类提供load,loadSingleloadFuture.

Kotlin 中的 load 示例使用协程改造加载数据,因此可以 return 一个 LoadResult 对象。但是对于 LiveData,我需要从改造中进行异步调用并在 LiveData 对象上设置值。 LiveData 没有单独的 load 实用方法,就像 RxJavaGuava 一样。那么,如何使用 Java 中的 LiveData 实现此目的?

package org.metabrainz.mobile.data.repository;

import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.paging.PagingSource;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.metabrainz.mobile.data.sources.Constants;
import org.metabrainz.mobile.data.sources.api.MusicBrainzServiceGenerator;
import org.metabrainz.mobile.data.sources.api.SearchService;
import org.metabrainz.mobile.data.sources.api.entities.mbentity.MBEntity;
import org.metabrainz.mobile.data.sources.api.entities.mbentity.MBEntityType;
import org.metabrainz.mobile.presentation.features.adapters.ResultItem;
import org.metabrainz.mobile.presentation.features.adapters.ResultItemUtils;

import java.util.ArrayList;
import java.util.List;

import kotlin.coroutines.Continuation;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class SearchPagingSource extends PagingSource<Integer, ResultItem> {

    @NonNull
    private final static SearchService service = MusicBrainzServiceGenerator
            .createService(SearchService.class, true);
    @NonNull
    private MBEntityType entity;
    @NonNull
    private String searchTerm;

    public SearchPagingSource(@NonNull MBEntityType entity, @NonNull String searchTerm) {
        this.entity = entity;
        this.searchTerm = searchTerm;
    }

    @NotNull
    @Override
    public LiveData<LoadResult<Integer, ResultItem>> load(@NotNull LoadParams<Integer> loadParams,
                         @NotNull Continuation<? super LoadResult<Integer, ResultItem>> continuation) {

        Integer pageSize = loadParams.getLoadSize();
        Integer offset = loadParams.getKey() == null ? 0 : loadParams.getKey();

        MutableLiveData<LoadResult<Integer, ResultItem>> resultsLiveData = new MutableLiveData<>();
        service.searchEntity(entity.name, searchTerm, pageSize.toString(),
                String.valueOf(offset * pageSize))
                .enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(@NonNull Call<ResponseBody> call,
                                           @NonNull Response<ResponseBody> response) {
                        try {
                            List<ResultItem> data = ResultItemUtils
                                    .getJSONResponseAsResultItemList(response.body().string(), entity);

                            LoadResult.Page<Integer, ResultItem> loadResult
                                    = new LoadResult.Page<>(data, Math.max(0, offset - pageSize),
                                    offset + pageSize, LoadResult.Page.COUNT_UNDEFINED,
                                    LoadResult.Page.COUNT_UNDEFINED);
                            resultsLiveData.setValue(loadResult);
                        } catch (Exception e) {
                            e.printStackTrace();
                            LoadResult.Error<Integer, ResultItem> error = new LoadResult.Error<>(e);
                            resultsLiveData.setValue(error);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {

                    }
                });
        return resultsLiveData;
    }


}

但是这会在运行时崩溃

org.metabrainz.android E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.metabrainz.android, PID: 2222
    java.lang.ClassCastException: androidx.lifecycle.MutableLiveData cannot be cast to androidx.paging.PagingSource$LoadResult
        at androidx.paging.PageFetcherSnapshot.doInitialLoad(PageFetcherSnapshot.kt:302)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow.invokeSuspend(PageFetcherSnapshot.kt:149)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow.invoke(Unknown Source:10)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow.invokeSuspend(CancelableChannelFlow.kt:35)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow.invoke(Unknown Source:10)
        at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo$suspendImpl(Builders.kt:327)
        at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo(Unknown Source:0)
        at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun.invokeSuspend(ChannelFlow.kt:33)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:321)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
        at androidx.lifecycle.BlockRunner.maybeRun(CoroutineLiveData.kt:174)
        at androidx.lifecycle.CoroutineLiveData.onActive(CoroutineLiveData.kt:240)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:437)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:395)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
        at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
        at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
        at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
        at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
        at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:144)
        at androidx.lifecycle.ReportFragment.onStart(ReportFragment.java:109)
        at android.app.Fragment.performStart(Fragment.java:2637)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1312)
        at android.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1549)
        at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1611)
        at android.app.FragmentManagerImpl.dispatchMoveToState(FragmentManager.java:3039)
        at android.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:2996)
        at android.app.FragmentController.dispatchStart(FragmentController.java:189)
        at android.app.Activity.performStart(Activity.java:7007)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2867)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2986)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1671)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:206)
        at android.app.ActivityThread.main(ActivityThread.java:6784)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:852)

我已经打开了一个功能请求,要求将 LiveData Paging Source 与 Paging Library 一起发送。我收到了 Google 开发人员的以下回复。

The Java Guava samples on d.android.com are for Guava + LiveData, in the coroutine equivalent we use: Guava's ListenableFuture as an async primitive that returns a single result (equivalent to a Coroutine or RxJava Single), and LiveData for multiple results / stream of results (equivalent of Kotlin Flow, or RxJava Observable).

如果我想使用 LiveData,建议我在以下代码片段的行上写一些东西。

abstract class SearchPagingSource extends RxPagingSource<Integer, ResultItem>() {

    @NotNull
    public abstract LiveData<LoadResult<Integer, ResultItem>> loadLiveData(params: LoadParams<Key>);

    @NotNull
    @Override
    public Single<LoadResult<Integer, ResultItem>> loadSingle(@NotNull LoadParams<Integer> loadParams) {
        return loadLiveData(params).toRxJavaSingle(); // You must implement this bit!
    }
}

上面代码段中使用的LiveData应该是SingleLiveEvent

PS:Google 开发人员愿意重新考虑他们的立场,如果更多开发人员提出要求,可以重新考虑将其运送到库中。相关的 Google 问题跟踪单是 this