Наблюдение LiveData из комнаты вызывает обновление PagedListAdapter, ViewModel обновляет данные также об изменениях ориентации
Я создаю приложение, которое использует ArticleBoundaryCallback, чтобы инициировать вызов API и хранить ответ в Room. Я также слушаю эту таблицу с помощью LiveData и отображаю элементы в PagedListAdapter.
Проблема в том, что каждый раз, когда новые данные вставляются в таблицу Room (Article), весь список обновляется.
Кроме того, при изменении конфигурации все данные, похоже, снова выбираются (ViewModel не сохраняет их, RecyclerView воссоздается).
При каждой вставке RecyclerView перепрыгивает (несколько строк, если вставлены новые данные, или в начале, если новые данные заменяются старыми).
Весь код находится в этом репозитории GitHub.
Мои занятия:
Статья:
@Entity(tableName = "article",
indices={@Index(value="id")})public class Article {
@PrimaryKey(autoGenerate = false)
@SerializedName("_id")
@Expose
@NonNull
private String id;
@SerializedName("web_url")
@Expose
private String webUrl;
@SerializedName("snippet")
@Expose
private String snippet;
@SerializedName("print_page")
@Expose
private String printPage;
@SerializedName("source")
@Expose
private String source;
@SerializedName("multimedia")
@Expose
@Ignore
private List<Multimedium> multimedia = null;
Статья ДАО:
@Dao
public interface ArticleDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insert(Article article);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void update(Article... repos);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertArticles(List<Article> articles);
@Delete
void delete(Article... articles);
@Query("DELETE FROM article")
void deleteAll();
@Query("SELECT * FROM article")
List<Article> getArticles();
@Query("SELECT * FROM article")
DataSource.Factory<Integer, Article> getAllArticles();}
LocalCache (сохраняет / получает из комнаты)
public class LocalCache {
private static final String TAG = LocalCache.class.getSimpleName();
private ArticleDao articleDao;
private Executor ioExecutor;
public LocalCache(AppDatabase appDatabase, Executor ioExecutor) {
this.articleDao = appDatabase.getArticleDao();
this.ioExecutor = ioExecutor;
}
public void insertAllArticles(List<Article> articleArrayList){
ioExecutor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "inserting " + articleArrayList.size() + " repos");
articleDao.insertArticles(articleArrayList);
}
});
}
public void otherFunction(ArrayList<Article> articleArrayList){
// TODO
}
public DataSource.Factory<Integer, Article> getAllArticles() {
return articleDao.getAllArticles();
}
AppRepository
public class AppRepository {
private static final String TAG = AppRepository.class.getSimpleName();
private static final int DATABASE_PAGE_SIZE = 20;
private Service service;
private LocalCache localCache;
private LiveData<PagedList<Article>> mPagedListLiveData;
public AppRepository(Service service, LocalCache localCache) {
this.service = service;
this.localCache = localCache;
}
/**
* Search - match the query.
*/
public ApiSearchResultObject search(String q){
Log.d(TAG, "New query: " + q);
// Get data source factory from the local cache
DataSource.Factory dataSourceFactory = localCache.getAllArticles();
// every new query creates a new BoundaryCallback
// The BoundaryCallback will observe when the user reaches to the edges of
// the list and update the database with extra data
ArticleBoundaryCallback boundaryCallback = new ArticleBoundaryCallback(q, service, localCache);
// Get the paged list
LiveData data = new LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.setBoundaryCallback(boundaryCallback)
.build();
mPagedListLiveData = data;
ApiSearchResultObject apiSearchResultObject = new ApiSearchResultObject();
apiSearchResultObject.setArticles(data);
return apiSearchResultObject;
}
public DataSource.Factory getAllArticles() {
return localCache.getAllArticles();
}
public void insertAllArticles(ArrayList<Article> articleList) {
localCache.insertAllArticles(articleList);
}
}
ViewModel
public class DBArticleListViewModel extends ViewModel {
private AppRepository repository;
// init a mutable live data to listen for queries
private MutableLiveData<String> queryLiveData = new MutableLiveData();
// make the search after each new search item is posted with (searchRepo) using "map"
private LiveData<ApiSearchResultObject> repositoryResult = Transformations.map(queryLiveData, queryString -> {
return repository.search(queryString);
});
// constructor, init repo
public DBArticleListViewModel(@NonNull AppRepository repository) {
this.repository = repository;
}
// get my Articles!!
public LiveData<PagedList<Article>> articlesLiveData = Transformations.switchMap(repositoryResult, object ->
object.getArticles());
// get teh Network errors!
public LiveData<String> errorsLiveData = Transformations.switchMap(repositoryResult, object ->
object.getNetworkErrors());
// Search REPO
public final void searchRepo(@NonNull String queryString) {
this.queryLiveData.postValue(queryString);
}
// LAST Query string used
public final String lastQueryValue() {
return (String)this.queryLiveData.getValue();
}
Деятельность - наблюдение от ВМ
DummyPagedListAdapter articleListAdapter = new DummyPagedListAdapter(this);
localDBViewModel = ViewModelProviders.of(this, Injection.provideViewModelFactory(this)).get(DBArticleListViewModel.class);
localDBViewModel.articlesLiveData.observe(this, pagedListLiveData ->{
Log.d(TAG, "articlesLiveData.observe size: " + pagedListLiveData.size());
if(pagedListLiveData != null)
articleListAdapter.submitList(pagedListLiveData);
});
recyclerView.setAdapter(articleListAdapter);
адаптер
public class DummyPagedListAdapter extends PagedListAdapter<Article, ArticleViewHolder> {
private final ArticleListActivity mParentActivity;
public DummyPagedListAdapter(ArticleListActivity parentActivity) {
super(Article.DIFF_CALLBACK);
mParentActivity = parentActivity;
}
@NonNull
@Override
public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mParentActivity).inflate(R.layout.article_list_content, parent, false);
return new ArticleViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ArticleViewHolder holder, int position) {
Article article = getItem(position);
if (article != null) {
holder.bindTo(article);
} else {
holder.clear();
}
}
}
DIFF
public static DiffUtil.ItemCallback<Article> DIFF_CALLBACK = new
DiffUtil.ItemCallback<Article>() {
@Override
public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull
Article newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull
Article newItem) {
return oldItem.getWebUrl() == newItem.getWebUrl();
}
};
Мне действительно нужно решить это. Спасибо!
1 ответ
Да.. это заняло у меня некоторое время, но я решил это. Как я и думал, глупая проблема: в DIFF_CALLBACK, используемом адаптером, чтобы решить, что добавить и что игнорировать из наблюдаемого набора данных, я использовал в качестве компаратора oldItem.getId() == newItem.getId(), которые являются строками!!! И, конечно же, адаптер всегда получал "новые значения" и добавлял их.
Исправлено DiffUtil.ItemCallback
public static DiffUtil.ItemCallback<Article> DIFF_CALLBACK = new DiffUtil.ItemCallback<Article>() {
@Override
public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
return oldItem.getStoreOrder() == newItem.getStoreOrder();
}
@Override
public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
return oldItem.getId().equals(newItem.getId()) && oldItem.getWebUrl().equals(newItem.getWebUrl());
}
};
Надеюсь, это будет напоминанием, чтобы всегда обращать внимание события на самые элементарные вещи. Я потерял кучу времени с этим. Надеюсь, вы не будете:)