【问题标题】:RecyclerView reload same data when refreshRecyclerView 刷新时重新加载相同的数据
【发布时间】:2017-04-19 14:32:58
【问题描述】:

我有一个问题,当我刷卡以刷新数据时,第一次刷卡没问题,但之后每次刷卡重新加载并一遍又一遍地添加相同的数据,最后我有一个列表一遍又一遍地包含相同的项目...我正在使用装载机。 我之前试图清除,但我不明白我的代码有什么问题,如果有人可以向我解释的话。谢谢。

这是我的代码:

public abstract class NewsFragment extends Fragment implements LoaderManager.LoaderCallbacks<ArrayList<Articles>> {

    protected ItemAdapter mArticleAdapter;
    protected RecyclerView mRecyclerView;
    protected NewsFragment.OnNewSelectedInterface mListener;
    protected RecyclerView.LayoutManager mManager;
    protected SwipeRefreshLayout mSwipeRefreshLayout;
    protected LoaderManager mLoaderManager;
    private boolean mStateSaved;

    private static final int NEWS_LOAD_ID = 1;
    public static final String KEY_LIST = "key_list";

    public interface OnNewSelectedInterface {
        void onListNewSelected(int index, ArrayList<Articles> articles);
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.list_present_news, container, false);

        mListener = (NewsFragment.OnNewSelectedInterface) getActivity();
        mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeContainer);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
        mManager = new LinearLayoutManager(getActivity());
        mArticleAdapter = new ItemAdapter(getActivity(), new ArrayList<Articles>(), mListener);
        mLoaderManager = getLoaderManager();
        mStateSaved = mArticleAdapter.isStateSaved();

        mRecyclerView.setAdapter(mArticleAdapter);
        mRecyclerView.setLayoutManager(mManager);

        getData();
        refreshData();

        if(!isNetworkAvailable())alertUserAboutError();

        setDivider();

        return view;
    }

    private void setDivider() {
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView
                .getContext(), DividerItemDecoration.VERTICAL);
        mRecyclerView.addItemDecoration(dividerItemDecoration);
    }

    private void getData() {
        getLoaderManager().initLoader(NEWS_LOAD_ID, null, this).forceLoad();
    }

    private void alertUserAboutError() {
        AlertDialogFragment alertDialogFragment = new AlertDialogFragment();
        alertDialogFragment.show(getActivity().getFragmentManager(), "error_dialog");
    }

    protected abstract String[] getUrl();

    private boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager)
                getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
        boolean isAvailable = false;
        if (networkInfo != null && networkInfo.isConnected()) {
            isAvailable = true;
        }
        return isAvailable;
    }

    private void refreshData() {
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mArticleAdapter.clear();
                mSwipeRefreshLayout.setRefreshing(false);

            }
        });

        mSwipeRefreshLayout.setColorSchemeResources(
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
    }

    @Override
    public Loader<ArrayList<Articles>> onCreateLoader(int id, Bundle args) {
        return new NewsLoader(getActivity(), getUrl());
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<Articles>> loader, ArrayList<Articles> data) {
        if (data != null && !data.isEmpty()) {
            mArticleAdapter.addAll(data);
        }
    }

    @Override
    public void onLoaderReset(Loader<ArrayList<Articles>> loader) {
        mArticleAdapter.clear();
    }
}

我的加载器类:

public class NewsLoader extends AsyncTaskLoader<ArrayList<Articles>>{

    private ArrayList<Articles> mArticlesArrayList;
    private String[] mUrl;

    public NewsLoader(Context context, String[] url) {
        super(context);
        mUrl = url;
    }

    @Override
    public ArrayList<Articles> loadInBackground() {

        OkHttpClient mClient = new OkHttpClient();
        for (String aMUrl : mUrl) {
            Request mRequest = new Request.Builder().url(aMUrl).build();
            try {
                Response response = mClient.newCall(mRequest).execute();
                try {
                    if (response.isSuccessful()) {
                        String json = response.body().string();
                        getMultipleUrls(json);
                    }
                } catch (IOException | JSONException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return mArticlesArrayList;
    }

    private void getMultipleUrls(String jsonData) throws JSONException {

        if (mArticlesArrayList == null) {
            mArticlesArrayList = getArticleForecast(jsonData);
        } else {
            mArticlesArrayList.addAll(getArticleForecast(jsonData));
        }
    }

    private ArrayList<Articles> getArticleForecast(String jsonData) throws JSONException {
        JSONObject forecast = new JSONObject(jsonData);
        JSONArray articles = forecast.getJSONArray("articles");

        ArrayList<Articles> listArticles = new ArrayList<>(articles.length());

        for (int i = 0; i < articles.length(); i++) {
            JSONObject jsonArticle = articles.getJSONObject(i);
            Articles article = new Articles();

            String urlImage = jsonArticle.getString("urlToImage");

            article.setTitle(jsonArticle.getString("title"));
            article.setDescription(jsonArticle.getString("description"));
            article.setImageView(urlImage);
            article.setArticleUrl(jsonArticle.getString("url"));

            listArticles.add(i, article);
        }

        return listArticles;
    }
}

我的适配器类:

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ArticleViewHolder> {

    private static final String TAGO = ItemAdapter.class.getSimpleName();
    private final NewsFragment.OnNewSelectedInterface mListener;
    private ArrayList<Articles> mArticlesList;
    private Context mContext;
    private int lastPosition = -1;
    private boolean mStateSaved = false;


    public boolean isStateSaved() {
        return mStateSaved;
    }

    public void setStateSaved(boolean stateSaved) {
        mStateSaved = stateSaved;
    }

    public ItemAdapter(Context context, ArrayList<Articles> articles, NewsFragment.OnNewSelectedInterface listener){
        mContext = context;
        mArticlesList = articles;
        mListener = listener;
    }

    @Override
    public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);

        ArticleViewHolder articleViewHolder = new ArticleViewHolder(view);
        articleViewHolder.setIsRecyclable(false);
        return articleViewHolder;
    }

    @Override
    public void onBindViewHolder(ArticleViewHolder holder, int position) {

        holder.bindArticle(mArticlesList.get(holder.getAdapterPosition()));
        setAnimation(holder.itemView, holder.getAdapterPosition());
    }

    private void setAnimation(View viewToAnimate, int position) {
        if (position > lastPosition) {
            Animation animation = AnimationUtils.loadAnimation(viewToAnimate.getContext(), android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            lastPosition = position;
        }

    }

    @Override
    public int getItemCount() {
        return mArticlesList.size();
    }

    public void clear() {
        mArticlesList.clear();
        notifyDataSetChanged();
    }

    public void addAll(ArrayList<Articles> articles) {
        mArticlesList.addAll(articles);
        notifyDataSetChanged();
    }


    protected class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        private ImageView mImageView;
        private TextView mTitleTextView, mDescriptionTextView;
        private FloatingActionButton mSaveButton;

        private ArticleViewHolder(View itemView) {
            super(itemView);

            mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
            mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
            mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
            mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);

            itemView.setOnClickListener(this);
        }

        private void bindArticle(final Articles article) {

            Glide.with(mContext).load(article.getImageView()).into(mImageView);
            mTitleTextView.setText(article.getTitle());
            mDescriptionTextView.setText(article.getDescription());
            if(mDescriptionTextView.getText().equals("")){
                mDescriptionTextView.setVisibility(View.GONE);
            }

            mSaveButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    insertArticle(article);
                    article.setStateSaved(true);
                }
            });

            Log.v(TAGO, "Item id : " + getItemId()
                    + "Item count : " + getItemCount()
                    + "Item position : " + getAdapterPosition()
                    + String.valueOf(article.isStateSaved()));
        }

        private void insertArticle(Articles articles) {

            String title = articles.getTitle();
            String description = articles.getDescription();
            String url = articles.getArticleUrl();

            ContentValues contentValues = new ContentValues();
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_TITLE_ARTICLE, title);
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_DESCRIPTION_ARTICLE, description);
            contentValues.put(ArticleContract.ArticleEntry.COLUMN_URL_ARTICLE, url);

            Uri uri = mContext.getContentResolver().insert(ArticleContract.ArticleEntry.CONTENT_URI, contentValues);

            if(uri == null) {
                Log.v(TAGO, "Error");
            } else Toast.makeText(mContext, "Article Saved", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onClick(View view) {
            mListener.onListNewSelected(getLayoutPosition(), mArticlesList);
        }

    }
}

【问题讨论】:

  • 看起来你的SwipeRefreshLayout 没有做任何事情。 onRefresh()方法只调用mArticleAdapter.clear()mSwipeRefreshLayout.setRefreshing(false);您实际上是如何尝试重新加载数据的?
  • 对不起,当试图修复错误时,我删除了一行,我调用 getData() ;在我的刷新方法中清除列表后立即
  • 每个Articles 对象是多个“文章”吗?如果不是,该课程不应该命名为Article 吗?不是什么大问题,只是一个令人困惑的单词选择。
  • 是的,它应该但它不能解决我的问题..
  • 你检查过你的 api 是否给你冗余数据?

标签: android android-recyclerview android-adapter loader swiperefreshlayout


【解决方案1】:

您错误地使用了ViewHolder#setIsRecyclable;此方法旨在用于防止 ViewHolder 在对其进行更改时被回收。根据文档:

呼叫setIsRecyclable() 应始终配对(一次呼叫 setIsRecyclabe(false) 应始终与稍后调用相匹配 setIsRecyclable(true))。

这意味着您的任何ViewHolder 对象都不会被回收,从而有效地使RecyclerView 的使用毫无价值,并在您尝试将新对象绑定到您的RecyclerView 时阻止它重用视图。

所以,简而言之,删除那行代码。


我还注意到您的适配器代码还有其他一些小问题,这可能会在未来引起很多人的头疼;所以我冒昧地强调了我将要做出的一些改变。

为了我自己的理智,我将把你的Articles 类称为Article

到处传递你的Context 通常不是一个好主意。传递给您的ViewHolderView 已经引用了Context,因此您可以使用它。

至于insertArticle() 代码,Activity 无论如何都应该处理这个问题。因此,您可以将Article 传递回Activity,方法是将侦听器传递给您的Adapter(随后是每个ViewHolder),而不是Context

您还应该考虑使用DiffUtil 类,而不仅仅是调用notifyDataSetChanged();它效率更高。只需确保您的 Article 类正在实现 equals()hashCode() ,否则它将无法工作。

我没有包含动画代码(可以很容易地重新添加)或保存的状态代码(主要是因为我不知道您要做什么)。

public class ArticleAdapter extends RecyclerView.Adapter<Article> {

    private List<Article> mData;

    private ArticleViewHolder.OnSelectedListener mOnSelectedListener;
    private ArticleViewHolder.OnSaveListener mOnSaveListener;

    public ArticleAdapter(ArticleViewHolder.OnSelectedListener onSelectedListener, ArticleViewHolder.OnSaveListener onSaveListener) {
        mOnSelectedListener = onSelectedListener;
        mOnSaveListener = onSaveListener;
        mData = new ArrayList<>();
    }

    public void replaceData(final List<Article> data) {
        final List<Article> oldData = new ArrayList<>(mData);
        mData.clear();

        if (data != null) {
            mData.addAll(data);
        }

        DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldData.size();
            }

            @Override
            public int getNewListSize() {
                return mData.size();
            }

            @Override
            public int areItemsTheSame(int oldItemPosition, int newItemPosition) {
                return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
            }
        }).dispatchUpdatesTo(this);
    }

    @Override
    public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);
        return new SelectLocationViewHolder(view, mOnSelectedListener, mOnSaveListener);
    }

    @Override
    public void onBindViewHolder(ArticleViewHolder holder, int position) {
        holder.bind(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

}

public class ArticleViewHolder extends RecyclerView.ViewHolder {

    public interface OnSelectedListener {
        void onSelected(Article article);
    }

    public interface OnSaveListener {
        void onSave(Article article);
    }

    private View mView;
    private Article mArticle;

    private OnSelectedListener mOnSelectedListener;
    private OnSaveListener mOnSaveListener;

    private ImageView mImageView;
    private TextView mTitleTextView, mDescriptionTextView;
    private FloatingActionButton mSaveButton;

    public ArticleViewHolder(View itemView, final OnSelectedListener onSelectedListener, final OnSaveListener onSaveListener) {
        super(itemView);

        mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
        mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
        mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
        mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);

        mView = itemView;
        mView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onSelectedListener.onSelected(mArticle);
            }
        });

        mSaveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onSaveListener.onSave(mArticle);
            }
        });
    }

    public void bind(Article article) {
        mArticle = article;
        mTitleTextView.setText(article.getTitle());

        mDescriptionTextView.setText(article.getDescription());
        if(TextUtils.isEmpty(article.getDescription())) {
            mDescriptionTextView.setVisibility(View.GONE);
        }

        Glide.with(mView.getContext()).load(article.getImage()).into(mImageView);
    }

}

编辑

实际问题是您的加载程序每次都使用相同的ArrayList,并不断向其中添加新结果。

public class NewsLoader extends AsyncTaskLoader<List<Article>> {

    private final String[] mUrls;
    private final OkHttpClient mClient;

    public NewsLoader(Context context, OkHttpClient client, String... urls) {
        super(context);
        mClient = client;
        mUrls = urls;
    }

    @Override
    public List<Article> loadInBackground() {
        List<Article> articles = new ArrayList<>();

        for (String url : mUrls) {
            Request request = new Request.Builder().url(url).build();
            try {
                Response response = mClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    parseData(response.body().string(), articles);
                }
            } catch (IOException | JSONException e) {
                e.printStackTrace();
            }
        }

         return articles;
    }

    private void parseData(List<Article> articles, String data) throws JSONException {
        JSONObject forecast = new JSONObject(data);
        JSONArray a = forecast.getJSONArray("articles");

        for (int i = 0; i < a.length(); i++) {
            JSONObject o = a.getJSONObject(i);
            Article article = new Article(
                    o.getString("title"),
                    o.getString("description"),
                    o.getString("url"),
                    o.getString("urlToImage"));
            articles.add(article);
        }
    }

}

另外,您可能已经注意到,我对您的 Article 构造函数做了一个小改动。您应该考虑将Article 类设为immutable,因为这将防止您在处理多线程时出错。它应该看起来像这样:

public class Article {

    private final String mTitle;
    private final String mDescription;
    private final String mUrl;
    private final String mImageUrl;


    public Article(String title, String description, String url, String imageUrl) {
        mTitle = title;
        mDescription = description;
        mUrl = url;
        mImageUrl = imageUrl;
    }

    public String title() {
        return mTitle;
    }

    public String description() {
        return mDescription;
    }

    public String url() {
        return mUrl;
    }

    public String imageUrl() {
        return mImageUrl;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Article other = (Article) o;

        return mTitle != null && mTitle.equals(other.mTitle) &&
                mDescription != null && mDescription.equals(other.mDescription) &&
                mUrl != null && mUrl.equals(other.mUrl) &&
                mImageUrl != null && mImageUrl.equals(other.mImageUrl);
    }

    @Override
    public int hashCode() {
        int result = mTitle != null ? mTitle.hashCode() : 0;
        result = 31 * result + (mDescription != null ? mDescription.hashCode() : 0);
        result = 31 * result + (mUrl != null ? mUrl.hashCode() : 0);
        result = 31 * result + (mImageUrl != null ? mImageUrl.hashCode() : 0);
        return result;
    }

}

【讨论】:

  • 谢谢,我会尽快尝试,感谢您抽出宝贵时间审查我的代码!我会告诉你的!!
  • 就像我在下面问的那样,您能否告诉我是否有更好的方法来插入数据而无需遍历我的 url..?谢谢
  • @samuelzaffran 我猜你在谈论你之前的问题; “Is there a better way to execute this code (load multiple urls)?”?如果是这样的话,你以后应该provide link to the question;要弄清楚您指的是哪个问题并不总是那么容易。
  • 谢谢,我不知道!但我试图删除这行代码,但仍然......每次我刷卡时我总是一遍又一遍地有更多数据......
  • @samuelzaffran Huh,除了可能使用ViewHolder#getAdapterPosition 之外,没有其他问题突出。尝试用我上面发布的代码替换您的Adapter,如果您仍然遇到同样的问题,则可能与您的网络代码有关。
【解决方案2】:
@Override
public void onBindViewHolder(ArticleViewHolder holder, int position) {
    holder.bindArticle(mArticlesList.get(position));
    setAnimation(holder.itemView, position);
}


public void addAll(ArrayList<Articles> articles) {
    mArticlesList.clear();
    mArticlesList.addAll(articles);
    notifyDataSetChanged();
}

如果这不起作用,那么我认为您的 api 正在为您提供冗余数据。 为什么你使用articleViewHolder.setIsRecyclable(false);

另一个可能导致问题的地方是

private void getMultipleUrls(String jsonData) throws JSONException {

    if (mArticlesArrayList == null) {
         mArticlesArrayList = getArticleForecast(jsonData);
    } else {
        mArticlesArrayList.addAll(getArticleForecast(jsonData));
    }
}

您从循环中调用它,将数据添加到您的数组列表中。不知何故,您的ArrayList中可以插入多个数据

【讨论】:

  • 不,我的 api 没有给我多余的数据,当我滑动刷新数据更改的顺序时,我调用了可回收的错误原因!你能告诉我如何在不循环的情况下插入吗?我搜索但没有找到有用的东西..谢谢
  • 首先您需要确定该方法是否将冗余数据放入列表中。
  • 我确定我没有一组 url(字符串)和每个 URL,正在向我发送一些文章,但它们每一篇都是独一无二的
猜你喜欢
  • 1970-01-01
  • 2018-08-05
  • 2020-08-12
  • 1970-01-01
  • 1970-01-01
  • 2012-07-31
  • 2011-11-29
  • 2019-07-12
  • 1970-01-01
相关资源
最近更新 更多