4

I have an app which has endless recyclerView (when certain offset to the end of list is reached, I call interface method from recyclerView adapter).
RecyclerView Adapter

public SearchResultAdapter(ArrayList<String> suggestions) {
    this.suggestions = suggestions;
}

public SearchResultAdapter(ArrayList<Word> results, RecyclerView recyclerView) {
    words = results;
    this.recyclerView = recyclerView;
    final LinearLayoutManager mLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 0) //check for scroll down
            {
                visibleItemCount = mLayoutManager.getChildCount();
                totalItemCount = mLayoutManager.getItemCount();
                pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
                if (loading) {
                    if ((visibleItemCount + pastVisiblesItems + 14) >= totalItemCount) {
                        if (mOnLoadMoreListener != null)
                            mOnLoadMoreListener.onLoadMore();
                        loading = false;
                    }
                }
            }
        }
    });
}

public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
    this.mOnLoadMoreListener = mOnLoadMoreListener;
}

public void setOnSearchResultClickListener(OnSearchResultClickListener mClickListener) {
    this.mClickListener = mClickListener;
}

public void setLoading() {
    loading = true;
}

@Override
public int getItemViewType(int position) {
   if(words !=null)
       return words.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    else 
       return VIEW_TYPE_SUGGESTION;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Context context = parent.getContext();
    LayoutInflater inflater = LayoutInflater.from(context);
    if (suggestions != null) {
        View view = inflater.inflate(R.layout.activity_suggestion_item, parent, false);
        return new SuggestionsViewHolder(view);
    }
    if (words != null) {
        // Inflate the custom layout
        if (viewType == VIEW_TYPE_ITEM) {
            View view = inflater.inflate(R.layout.activity_main_search_result_item, parent, false);
            return new WordViewHolder(view);
        }
        //inflate loading
        else if (viewType == VIEW_TYPE_LOADING) {
            View view = inflater.inflate(R.layout.activity_main_search_result_loading, parent, false);
            return new LoadingViewHolder(view);
        }
    }
    return null;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    Word word=null;
    if(words !=null)
    word = words.get(position);
    if (viewHolder instanceof LoadingViewHolder) {
        LoadingViewHolder holder = (LoadingViewHolder) viewHolder;
        holder.progressBar.setIndeterminate(true);
    } else if (viewHolder instanceof WordViewHolder) {
        WordViewHolder holder = (WordViewHolder) viewHolder;
        holder.wordTextView.setText(word.getTitle());
        holder.sourceTextView.setText(word.getSource().getTitle());
    } else if (viewHolder instanceof SuggestionsViewHolder){
        SuggestionsViewHolder holder = (SuggestionsViewHolder) viewHolder;
        holder.wordTextView.setText(suggestions.get(position));
    }
    //wordTextView.loadDataWithBaseURL(null,result.toString(), "text/html", "utf-8", null);
}

@Override
public int getItemCount() {
    return words == null ? (suggestions ==null?0:suggestions.size()) : words.size();
}

public static class LoadingViewHolder extends RecyclerView.ViewHolder {
    public ProgressBar progressBar;
    public LoadingViewHolder(View itemView) {
        super(itemView);
        progressBar = (ProgressBar) itemView.findViewById(R.id.search_item_progressbar);
    }
}

public class WordViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    // public TextView titleTextView;
    private CustomTextView wordTextView;
    private CustomTextView sourceTextView;
    private CardView searchResultItem;
    public WordViewHolder(View itemView) {
        super(itemView);
        searchResultItem = (CardView) itemView.findViewById(R.id.search_result_item_cardview);
        wordTextView = (CustomTextView) itemView.findViewById(R.id.search_result_item_body);
        sourceTextView = (CustomTextView) itemView.findViewById(R.id.search_result_source);
        searchResultItem.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
       mClickListener.onSearchResultClick(getAdapterPosition());
    }
}

public class SuggestionsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    // public TextView titleTextView;
    private CustomTextView wordTextView;
    private CardView suggestionItem;
    public SuggestionsViewHolder(View itemView) {
        super(itemView);
        suggestionItem = (CardView) itemView.findViewById(R.id.suggestion_item_cardview);
        wordTextView = (CustomTextView) itemView.findViewById(R.id.activity_suggestion_title);
        wordTextView.setTypeface(MainActivity.custom_font);
        suggestionItem.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
            mClickListener.onSuggestionClick(suggestions.get(getAdapterPosition()));
    }
}
@Override
public int getItemViewType(int position) {
   if(words !=null)
       return words.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    else 
       return VIEW_TYPE_SUGGESTION;
}

OnLoadMore

 @Override
public void onLoadMore() {
    if (request.getPage() < totalPageAmount) {
        request.nextPage();
        LoadMoreTask task = new LoadMoreTask(request, words, adapter);
        task.execute();
    }
}

LoadMoreTask

 public LoadMoreTask(Request request, ArrayList<Word> words, SearchResultAdapter adapter){
    this.request=request;
    this.words=words;
    this.adapter=adapter;
    this.connection = new Connection(request.getUrl());
}

@Override
protected void onPreExecute() {
    super.onPreExecute();
    if(words.get(words.size()-1)!=null){
        words.add(null);
        index = words.size() - 1;
        adapter.notifyItemInserted(index);
    }
}

@Override
protected Void doInBackground(Void... params) {
    connection.connect();
    String result = connection.getStringFromServer();
    Respond respond = new Respond(result);
    if(!request.isDetailed())
        words.addAll(respond.handleSimple());
    return null;
}

@Override
protected void onPostExecute(Void aVoid) {
    super.onPostExecute(aVoid);
    connection.disconnect();
    if(index!=0 && words.get(index)==null){
        words.remove(index);
        adapter.notifyItemRemoved(index+1);
    }
    adapter.notifyDataSetChanged();
    adapter.setLoading();
}

As it can be seen, when onLoadMore triggered, I connect to the webserver get more data and attach it to the previous arraylist. Adding null object in preExecute is done in order to change viewHolder type (I have little loading progressBar in the end of list, and it is deleted after the asynctask is finished). This code works pretty well, except that notifyDataSetChanged freezes UI a little bit. Yes it is small enough and seen only if you scroll too fast, but anyway it is annoying. How I can optimize this code to remove freezes?

rahimli
  • 1,373
  • 11
  • 25
  • Out of curiosity, y are you adding null to the `words`?? – Sanoop Surendran Jun 26 '16 at 01:58
  • Paste your adapter code. You must be performing some heavy operations in your onBindViewHolder – user3215142 Jun 26 '16 at 09:20
  • @Sanoop yes, as I described it is done in order to then differentiate whether it is loading or searchResult viewholder. – rahimli Jun 26 '16 at 09:55
  • @user3215142 I updated the code. There is selection of viewholder done in the OnBindView, but it is not the problem from my point of view, because the freezing is noticable only during loading process. So for example if I reach the end of list and there is no further loadings, it works perfectly without freezes. But onBindView is called continiously. So we can conclude that the problem is in notifyDataSetChanged – rahimli Jun 26 '16 at 09:59
  • notifyDataSetChanged calls onBindViewAdapter for each item in your words list. In postExecute you are calling setLoading which sets loading to true. Since loading has finished i think you want to set it to false. I think the scrollListener is triggered by notifyDataSetChanged and so loadMore is getting called in a loop. – user3215142 Jun 26 '16 at 10:29
  • no actually the code working ok, and there is no any infinite loop, but still there is small freezing on ui when data is loaded and adapter updates. there is reverse logic with loading variable :) – rahimli Jun 26 '16 at 10:31

1 Answers1

3

I solved the problem using notifyItemRangeInserted function, it seems that this function needs less resources than notifyDatasetChanged

@Override
protected void onPostExecute(Void aVoid) {
    super.onPostExecute(aVoid);
    connection.disconnect();
    adapter.resetLoading();
    if(index!=0 && words.get(index)==null){
        words.remove(index);
        adapter.notifyItemRemoved(index);
    }
    adapter.notifyItemRangeInserted(index,30);
}

So using this code everything works perfectly without any even nano freezes.

rahimli
  • 1,373
  • 11
  • 25