分页库使数据源无效不起作用

Paging Library invalidating data source not working

最近我在尝试这个:

我有一个由数据源支持的作业列表(我正在使用分页库)并且作业列表中的每个项目都有一个保存按钮,并且该保存按钮将作业的状态从未保存更新为已保存(或反之亦然)在数据库中,一旦更新它就会使数据源失效,现在失效应该会立即导致当前页面重新加载,但这并没有发生。

我检查了数据库中的值,它们实际上得到了更新,但 UI 的情况并非如此。

代码:

public class JobsPagedListProvider {

private JobListDataSource<JobListItemEntity> mJobListDataSource;

public JobsPagedListProvider(JobsRepository jobsRepository) {
    mJobListDataSource = new JobListDataSource<>(jobsRepository);
}

public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
    return new LivePagedListProvider<Integer, JobListItemEntity>() {
        @Override
        protected DataSource<Integer, JobListItemEntity> createDataSource() {
            return mJobListDataSource;
        }
    };
}

public void setQueryFilter(String query) {
    mJobListDataSource.setQuery(query);
}
}

这是我的自定义数据源:

public class JobListDataSource<T> extends TiledDataSource<T> {

private final JobsRepository mJobsRepository;
private final InvalidationTracker.Observer mObserver;


String query = "";

@Inject
public JobListDataSource(JobsRepository jobsRepository) {
    mJobsRepository = jobsRepository;

    mJobsRepository.setJobListDataSource(this);

    mObserver = new InvalidationTracker.Observer(JobListItemEntity.TABLE_NAME) {
        @Override
        public void onInvalidated(@NonNull Set<String> tables) {
            invalidate();
        }
    };

    jobsRepository.addInvalidationTracker(mObserver);
}

@Override
public boolean isInvalid() {
    mJobsRepository.refreshVersionSync();
    return super.isInvalid();
}

@Override
public int countItems() {
    return DataSource.COUNT_UNDEFINED;
}


@Override
public List<T> loadRange(int startPosition, int count) {
    return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}


public void setQuery(String query) {
    this.query = query;
}
}

这是 JobsRepository 中将作业从未保存更新为已保存的代码:

public void saveJob(JobListItemEntity entity) {
    Completable.fromCallable(() -> {
        JobListItemEntity newJob = new JobListItemEntity(entity);
        newJob.isSaved = true;
        mJobDao.insert(newJob);
        Timber.d("updating entity from " + entity.isSaved + " to "
                + newJob.isSaved); //this gets printed in log
        //insertion in db is happening as expected but UI is not receiving new list
        mJobListDataSource.invalidate();
        return null;
    }).subscribeOn(Schedulers.newThread()).subscribe();
}

这是工作列表的差异逻辑:

private static final DiffCallback<JobListItemEntity> DIFF_CALLBACK =  new DiffCallback<JobListItemEntity>() {
    @Override
    public boolean areItemsTheSame(@NonNull JobListItemEntity oldItem, @NonNull JobListItemEntity newItem) {
        return oldItem.jobID == newItem.jobID;
    }

    @Override
    public boolean areContentsTheSame(@NonNull JobListItemEntity oldItem, @NonNull JobListItemEntity newItem) {
        Timber.d(oldItem.isSaved + " comp with" + newItem.isSaved);
        return oldItem.jobID == newItem.jobID
                && oldItem.jobTitle.compareTo(newItem.jobTitle) == 0
                && oldItem.isSaved == newItem.isSaved;
    }
};

JobRepository 中的JobListDataSource(以下仅提及相关部分):

public class JobsRepository {
//holds an instance of datasource
private JobListDataSource mJobListDataSource;

//setter
public void setJobListDataSource(JobListDataSource jobListDataSource) {
    mJobListDataSource = jobListDataSource;
}

}

JobsRepository 中的 getJobs():

public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
    if (!isJobListInit) {

        Observable<JobList> jobListObservable = mApiService.getOpenJobList(
                mRequestJobList.setPageNo(startPosition/count + 1)
                        .setMaxResults(count)
                        .setSearchKeyword(query));

        List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);

        //make a synchronous network call since we have no data in db to return
        if(jobs.size() == 0) {
            JobList jobList = jobListObservable.blockingSingle();
            updateJobList(jobList, startPosition);
        } else {
            //make an async call and return cached version meanwhile
            jobListObservable.subscribe(new Observer<JobList>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(JobList jobList) {
                    updateJobList(jobList, startPosition);
                }

                @Override
                public void onError(Throwable e) {
                    Timber.e(e);
                }

                @Override
                public void onComplete() {

                }
            });
        }
    }

    return mJobDao.getJobsLimitOffset(count, startPosition);
}

jobsRepository 中的更新JobList:

private void updateJobList(JobList jobList, int startPosition) {
    JobListItemEntity[] jobs = jobList.getJobsData();
    mJobDao.insert(jobs);
    mJobListDataSource.invalidate();
}

看了DataSource的源码后,我明白了:

  1. DataSource 一旦失效将永远不会再次有效。
  2. invalidate() 说:如果 invalidate 已经被调用,这个方法什么都不做。

我实际上有一个由 JobsPagedListProvider 提供的自定义数据源 (JobListDataSource) 的单例,所以当我在 saveJob() 中使我的 DataSource 无效时(定义在JobsRepository),它试图获取新的 DataSource 实例(通过再次调用 loadRange() 来获取最新数据——这就是刷新 DataSource 的方式) 但是由于我的 DataSource 是单例并且它已经无效所以没有进行 loadRange() 查询!

因此请确保您没有 DataSource 的单例并手动(通过调用 invalidate())使您的 DataSource 无效,或者在您的 InvalidationTracker 中有一个 InvalidationTracker DataSource 的构造函数。

所以最终的解决方案是这样的:

JobsPagedListProvider 中没有单例:

public class JobsPagedListProvider {

private JobListDataSource<JobListItemEntity> mJobListDataSource;

private final JobsRepository mJobsRepository;

public JobsPagedListProvider(JobsRepository jobsRepository) {
    mJobsRepository = jobsRepository;
}

public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
    return new LivePagedListProvider<Integer, JobListItemEntity>() {
        @Override
        protected DataSource<Integer, JobListItemEntity> createDataSource() {
            //always return a new instance, because if DataSource gets invalidated a new instance will be required(that's how refreshing a DataSource works)          
            mJobListDataSource = new JobListDataSource<>(mJobsRepository);
            return mJobListDataSource;
        }
    };
}

public void setQueryFilter(String query) {
    mJobListDataSource.setQuery(query);
}
}

还要确保如果您从网络中获取数据,您需要有正确的逻辑来在查询网络之前检查数据是否过时,否则它会在每次 DataSource 失效时重新查询。 我通过在 JobEntity 中设置一个 insertedAt 字段来解决它,该字段跟踪该项目何时插入数据库并检查它是否在 JobsRepositorygetJobs() 中过时。

这是 getJobs() 的代码:

public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
    Observable<JobList> jobListObservable = mApiService.getOpenJobList(
            mRequestJobList.setPageNo(startPosition / count + 1)
                    .setMaxResults(count)
                    .setSearchKeyword(query));

    List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);

    //no data in db, make a synchronous call to network to get the data
    if (jobs.size() == 0) {
        JobList jobList = jobListObservable.blockingSingle();
        updateJobList(jobList, startPosition, false);
    } else if (shouldRefetchJobList(jobs)) {
        //data available in db, so show a cached version and make async network call to update data
        jobListObservable.subscribe(new Observer<JobList>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(JobList jobList) {
                updateJobList(jobList, startPosition, true);
            }

            @Override
            public void onError(Throwable e) {
                Timber.e(e);
            }

            @Override
            public void onComplete() {

            }
        });
    }

    return mJobDao.getJobsLimitOffset(count, startPosition);
}

最后删除 JobListDatasource 中的 InvalidationTracker,因为我们正在手动处理失效:

public class JobListDataSource<T> extends TiledDataSource<T> {

private final JobsRepository mJobsRepository;

String query = "";

public JobListDataSource(JobsRepository jobsRepository) {
    mJobsRepository = jobsRepository;
    mJobsRepository.setJobListDataSource(this);
}

@Override
public int countItems() {
    return DataSource.COUNT_UNDEFINED;
}

@Override
public List<T> loadRange(int startPosition, int count) {
    return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}


public void setQuery(String query) {
    this.query = query;
}
}