-2

I have an app with a RecyclerView and an customized Adapter which gets that from a parse backend.

My adapter contains a few TextViews, a Button, a small ImageView. Some of my TextViews require a function (calculating upvote count and stuff like this) in my Adapter's onBindViewHolder() to get their data.

I've noticed that my scrolling is really slow and laggy because of the functions that are called in onBindViewHolder().

Where should I set my data and call my functions in order to get a smooth scrolling ?

EDIT 1:

Here's my Adapter Code:

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
    public TextView     postName;
    public ImageView    postCatIcon;

    public TextView     postDays;
    public TextView     postDaysLabel;
    public TextView     postHours;
    public TextView     postHoursLabel;
    public TextView     postMinutes;
    public TextView     postMinutesLabel;
    public TextView     postDistance;

    public TextView     postLikes;
    public TextView     postAuthor;
    public LikeButton   likepost;

    private Post post;


    public ViewHolder(View itemView) {
        super(itemView);
        postName    = (TextView) itemView.findViewById(R.id.post_name);
        postCatIcon = (ImageView) itemView.findViewById(R.id.post_cat_icon);

        postDays            = (TextView) itemView.findViewById(R.id.post_days);
        postDaysLabel       = (TextView) itemView.findViewById(R.id.post_days_label);
        postHours           = (TextView) itemView.findViewById(R.id.post_hours);
        postHoursLabel      = (TextView) itemView.findViewById(R.id.post_hours_label);
        postMinutes         = (TextView) itemView.findViewById(R.id.post_minutes);
        postMinutesLabel    = (TextView) itemView.findViewById(R.id.post_minutes_label);
        postDistance        = (TextView) itemView.findViewById(R.id.post_distance);

        likePost    = (LikeButton) itemView.findViewById(R.id.like_the_post);
        postAuthor  = (TextView) itemView.findViewById(R.id.post_author);
        postLikes   = (TextView) itemView.findViewById(R.id.post_likes);
    }

    public void setData(Post post){
        this.post = post;
        updateTimeRemaining(System.currentTimeMillis());
    }

    public void updateTimeRemaining(long currentTime) {
        long diff = post.getDate().getTime() - currentTime;

        int minutes = (int) ((diff / (1000*60)) % 60);
        int hours   = (int) ((diff / (1000*60*60)) % 24);
        int days = (int) (diff / (1000*60*60*24));


        String strgMinutes = "00";
        String strgHours = "00";
        String strgDays = "00";

        if(minutes < 10)
            strgMinutes = "0" + String.valueOf(minutes);
        else
            strgMinutes = String.valueOf(minutes);

        if(hours < 10)
            strgHours = "0" + String.valueOf(hours);
        else
            strgHours = String.valueOf(hours);

        if(days < 10){
            strgDays = "0" + String.valueOf(days);
        }else
            strgDays = String.valueOf(days);

        postDays.setText(strgDays);
        postHours.setText(strgHours);
        postMinutes.setText(strgMinutes);
    }
}

public static class ProgressViewHolder extends PostAdapter.ViewHolder {
    public ProgressBar progressBar;

    public ProgressViewHolder(View v) {
        super(v);
        progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
    }
}

private List<Post> mPosts;
private Context mContext;
private ListFragment mFragment;

private final int VIEW_ITEM = 1;
private final int VIEW_PROG = 0;

ArrayList<PostAdapter.ViewHolder> viewHoldersList;
private Handler handler = new Handler();
private Runnable updateRemainingTimeRunnable = new Runnable() {
    @Override
    public void run() {
        long currentTime = System.currentTimeMillis();
        synchronized (viewHoldersList) {
            for (PostAdapter.ViewHolder holder : viewHoldersList) {
                holder.updateTimeRemaining(currentTime);
            }
        }
        handler.postDelayed(this, 60000);
    }
};

// The minimum amount of items to have below your current scroll position before loading more.
private int visibleThreshold = 2;
private int lastVisibleItem, totalItemCount;
private boolean loading;
private OnLoadMoreListener onLoadMoreListener;

public PostAdapter(List<Post> posts, Context context, ListFragment fragment, RecyclerView recyclerView) {
    mContext = context;
    mPosts = posts;
    mFragment = fragment;

    if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    // End has been reached
                    // Do something
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    loading = true;
                }
            }
        });
    }

    viewHoldersList = new ArrayList<>();
    startUpdateTimer();
}

public PostAdapter(List<Post> posts, Context context, RecyclerView recyclerView) {
    mContext = context;
    mPosts = posts;

    if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    // End has been reached
                    // Do something
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    loading = true;
                }
            }
        });
    }
    viewHoldersList = new ArrayList<>();
    startUpdateTimer();
}

private void startUpdateTimer() {
    handler.postDelayed(updateRemainingTimeRunnable,60000);
}

public PostAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Context context         = parent.getContext();
    LayoutInflater inflater = LayoutInflater.from(context);
    View postView;
    ViewHolder viewHolder;

    if (viewType == VIEW_ITEM) {
        postView = inflater.inflate(R.layout.list_item, parent, false);

         viewHolder = new ViewHolder(postView);
    } else {
        postView = inflater.inflate(R.layout.loading_item, parent, false);

        viewHolder = new ProgressViewHolder(postView);
    }

    return viewHolder;
}

public void onBindViewHolder(final PostAdapter.ViewHolder viewHolder, final int position) {
    if (viewHolder instanceof ViewHolder) {
        final Post post = mPosts.get(position);
        if(post != null){
            synchronized (viewHoldersList) {
                viewHolder.setData(post);
                viewHoldersList.add(viewHolder);
            }

            Category cat                    = post.getCategory();
            TextView nameTextView           = viewHolder.postName;
            TextView authorTextView         = viewHolder.postAuthor;
            final TextView likesTextView    = viewHolder.postLikes;
            ImageView CatIconView           = viewHolder.postCatIcon;
            final LikeButton likeButtonView = viewHolder.likePost;
            TextView postDistance           = viewHolder.postDistance;
            ParseUser postAuthor = null;

            if(mFragment != null) {
                if (mFragment.getIfFilteredByDistance()) {
                    postDistance.setVisibility(View.VISIBLE);
                    Location postLocation = new Location("Post location");
                    if(post.getLocation() != null){
                        postLocation.setLongitude(post.getLocation().getLongitude());
                        postLocation.setLatitude(post.getLocation().getLatitude());

                        GPSTracker gpsTracker = new GPSTracker(mContext);
                        if (gpsTracker.canGetLocation()) {
                            Location myLocation = new Location("My location");
                            myLocation.setLatitude(gpsTracker.getLatitude());
                            myLocation.setLongitude(gpsTracker.getLongitude());

                            postDistance.setText((Math.round(myLocation.distanceTo(postLocation)) / 1000) + " km");
                        } else
                            gpsTracker.showSettingsAlert();
                    }
                } else
                    postDistance.setVisibility(View.INVISIBLE);
            }


            try{
                postAuthor = post.getAuthor().fetchIfNeeded();
            }catch (ParseException e){
                Log.e("Parse:", e.getMessage());
            }

            saveActivitiesToLocalDatastore(post);

            final int likeCount = ((PostApplication)mContext).getPostLikeCount(post);
            likesTextView.setText(String.valueOf(likeCount));

            likeButtonView.setLiked(((PostApplication)mContext).getIfLiked(post));

            viewHolder.itemView.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(mContext, DetailActivity.class);
                    intent.putExtra("PostID",post.getObjectId());
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    mContext.startActivity(intent);
                }
            });

            likeButtonView.setOnLikeListener(new OnLikeListener() {
                @Override
                public void liked(LikeButton likeButton) {
                    if(((PostApplication)mContext).likePost(post))
                        likesTextView.setText(String.valueOf(Integer.parseInt(likesTextView.getText().toString()) + 1));
                }

                @Override
                public void unLiked(LikeButton likeButton) {
                    if(((PostApplication)mContext).unlikePost(post))
                        likesTextView.setText(String.valueOf(Integer.parseInt(likesTextView.getText().toString()) -1));

                }
            });


            nameTextView.setText(post.getTitle());
            authorTextView.setText(post.getAuthor().getUsername());


            CatIconView.setImageDrawable(ContextCompat.getDrawable(mContext,mContext.getResources().getIdentifier(cat.getIconName()+"_18dp","drawable",mContext.getPackageName())));

        }
    } else {
        ((ProgressViewHolder) viewHolder).progressBar.setIndeterminate(true);
    }
}



public int getItemCount() {
    return mPosts.size();
}

private void saveActivitiesToLocalDatastore(final Post post){
    // Saves Likes to Parse local datastore
}

@Override
public int getItemViewType(int position) {
    return mposts.get(position) != null ? VIEW_ITEM : VIEW_PROG;
}


public void setLoaded() {
    loading = false;
}

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

public interface OnLoadMoreListener {
    void onLoadMore();
}

EDIT 2:

I followed this in order to fix my countdown's flickering:

Recyclerview with multiple countdown timers causes flickering

EDIT 3:

I tried running the app without the additionals methods like saveActivitiesInBackgroud, and the listeners and it works really smooth

Community
  • 1
  • 1
schwdim
  • 193
  • 1
  • 2
  • 15
  • 2
    Post the code please. – Enzokie Sep 07 '16 at 06:41
  • 1
    The answer to that depends on where you store the downloaded data. Realm? SQLite? In-memory? Shared pref (lol)? – EpicPandaForce Sep 07 '16 at 06:41
  • 1
    RecyclerView is not laggy. Probably you have some mistake in your adapter code. Please send your codes, so people could help you. – Eren Sep 07 '16 at 06:42
  • Sorry, posted the code just now – schwdim Sep 07 '16 at 07:00
  • 1
    You probably shouldn't create a new GpsTracker per every single displayed element, that might help a bit. Also consider not using `synchronized` – EpicPandaForce Sep 07 '16 at 07:09
  • @EpicPandaForce I used synchronized to avoid creating a new CountDownTimer on every OnBindViewHolder() which created more lags. How should I do that ? And I tried without the GPSTracker and my saveActivitiesToLocalDatastore and the list is still not smooth – schwdim Sep 07 '16 at 07:13
  • 1
    hmm, what is the size of `viewHoldersList`? are you sure its 10 or something and not some big number like 10000 or so? – pskink Sep 07 '16 at 07:13
  • @pskink viewHoldersList is 6 at the start of the app and adds one everytime a new post is visible in the list – schwdim Sep 07 '16 at 07:20
  • 1
    are you sure? you call `viewHoldersList.add` each time `onBindViewHolder` is called but i dont see anywhere you delete from it? just add `Log.d` inside `onBindViewHolder` and double check it (of course you need do scroll your `RV` a bit so `onBindViewHolder` is called multiple times) – pskink Sep 07 '16 at 07:22
  • @pskink You're right, I never remove anything from it, it only get's bigger everytime I scroll, when should I remove objects from it ? – schwdim Sep 07 '16 at 07:25
  • 1
    You shouldn't even add anything to it directly, you should just call `adapter.notifyDataSetChanged()` when the timer reaches 0 – EpicPandaForce Sep 07 '16 at 07:25
  • @pskink I don't quite understand what you mean, but my timers are most likely not to expire while on the view, with values like 100 days so I doubt it will increase performances also, I tried running the app only with the timers and not any other TextView and It runs really smooth – schwdim Sep 07 '16 at 07:28
  • 1
    yeah, @EpicPandaForce is right: update your data model first and them call `notifyDataSetChanged` / `notifyItemChanged` / `notifyItemRangeChanged` – pskink Sep 07 '16 at 07:31
  • 1
    @Archipel your UI freezes because you synchronize between `onBindViewHolder()` and that iteration that updates the updatedTime in the holders – EpicPandaForce Sep 07 '16 at 07:33
  • @pskink Okay I will try that, would you by any chance know an example where they use those methods ? – schwdim Sep 07 '16 at 07:33
  • @EpicPandaForce so I shouldn't get rid of synchronize ? where should I call it then ? – schwdim Sep 07 '16 at 07:36
  • You shouldn't even have `synchronized` in here, this is a RecyclerView's Adapter – EpicPandaForce Sep 07 '16 at 07:46
  • @EpicPandaForce so the link I posted under my second edit also wrong ? I'm kinda confused right now – schwdim Sep 07 '16 at 07:48
  • This is a good time to start [performance profiling](https://developer.android.com/studio/profile/index.html) :) (here's a [codelabs](https://codelabs.developers.google.com/codelabs/android-perf-testing/index.html?index=..%2F..%2Findex#0)) – EpicPandaForce Sep 07 '16 at 08:01
  • you don't need `synchronized` bacause all happening on UI thread. – j2ko Sep 07 '16 at 08:09

2 Answers2

2

If u want use RecyclerView u need to use onBindViewHolder. Its not problem with OnBindViewHolder. Problem is with ur spaghetti code and also u not store the informations, so this cause ur recyclerview to lags

Rodriquez
  • 981
  • 1
  • 7
  • 21
  • Could you please elaborate, like what are the big mistakes with my code and how should I store the informations ? I'm aware I have to use onBindViewHolder I was just asking how I get lighten it. – schwdim Sep 07 '16 at 07:08
  • -U call sometimes informations that can be create once(new Objects). -U can store informations that using for further(SharedPreferences, Database u probally can go with error OUT-OF-Memory Try using LeakCanary and see what is leaked – Rodriquez Sep 07 '16 at 07:13
  • I tried LeakCanary, it doesn't notify me of anything – schwdim Sep 07 '16 at 07:32
1
  • Try not to change ViewHolder state outside of onBindViewHolder(). ViewHolder is representing view on screen but not representing data itself. RecyclerView tend to reuse views when they become invisible on screen and as result reuse ViewHolder attached to that view.

  • Never store ViewHolder - it's RecyclerView responsibility to manage them.

  • If you update your timer only once in 60 sec just notifyDataSetChanges instead of directly update ViewHolder. And update view state in onBindViewHolder with proper time.

j2ko
  • 2,479
  • 1
  • 16
  • 29
  • Okay I think I got the part about ViewHolder, but where am I supposed to call notifyDataSetChanges ? And Should I use a CountDownTimer for my timer in this case ? – schwdim Sep 07 '16 at 08:02
  • call it in `updateRemainingTimeRunnable::run` instead of looping over view holders. – j2ko Sep 07 '16 at 08:04
  • Okay so i'm keeping my updateTimeRemaining() method in my viewholder and getting rid of the setData() and all the synchronized is that correct ? I'm sorry I'm new to android developping and I'm not to familiar with those kinds of things. But thanks for your help – schwdim Sep 07 '16 at 08:13
  • my opinion is to move update time logic entirely outside of adapter. This timer belong to data. So in timer code you will call something like 'adapter.notifyDataSetChanged()'. Next remove 'instanceof ViewHolder -recycleview guarant that it is correct object. Move load more outside of adapter. It's not adapter responsibility to track that. – j2ko Sep 07 '16 at 08:23
  • Is there anyway I could contact you so I can show my code before I used all that synchronized and ViewHolder methods and maybe you could help me adapt my previous code with notifyDataSetChanged() ? – schwdim Sep 07 '16 at 08:29
  • Post code to the github, and post the link if it possibl. Or other public resource so i could grab and check on my side – j2ko Sep 07 '16 at 08:38
  • Okay, I will create a sample app and share it with you – schwdim Sep 07 '16 at 08:50
  • I created an app without login and other stuff but which charges the same list. If you have some time and help me with some best practice [GitLab Repo](https://gitlab.com/schwdim/MyApp/tree/master) – schwdim Sep 07 '16 at 13:18