Наблюдение за 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:

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

Репозиторий приложений

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

Мне действительно нужно решить эту проблему. Спасибо!


person jorjSB    schedule 21.07.2018    source источник
comment
Обновление: проблема связана с тем, как я использую Repository / ViewModel. Я просто попытался прослушать поле DB через репозиторий без применения преобразований, и он работает, как ожидалось!   -  person jorjSB    schedule 22.07.2018


Ответы (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());
    }
};

Надеюсь, это будет напоминанием о том, что всегда нужно уделять внимание самым элементарным вещам. Я потерял кучу времени на этом. Надеюсь, нет :)

person jorjSB    schedule 23.07.2018