Picasso keeps reloading images while scrolling upwards in listview, loads slowly

24x7 picture 24x7 · Jan 5, 2015 · Viewed 10.5k times · Source

I have been searching SO threads for answers but couldn't figure out my issue from previous discussion. I have a listview which loads about 50 images (it used to be about 100 but this was barely loading any images at all). After grabbing my JSON content (including image URL) from an api endpoint, through an adapter, my code puts it inside the listview.

Currently, with 50 images, picasso will load one image at a time as I scroll down on the feed. I feel as if keeping the scroll fixed on one item in the listview will make that image load faster. As I scroll up, however, it puts the placeholder back in and reloads the image again. Is there a way to solve this issue?

public class MainActivity extends Activity {
    private List<Post> myPosts = new ArrayList<Post>();
    protected String[] mBlogPostTitles;
    public static final String TAG = MainActivity.class.getSimpleName();//prints name of class without package name

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(isNetworkAvailable()) {
            GetBlogPostsTask getBlogPostsTask = new GetBlogPostsTask(); // new thread
            getBlogPostsTask.execute();// don't call do in background directly
        }else{
            Toast.makeText(this, "Network is unavailable", Toast.LENGTH_LONG).show();
        }
    }
    public boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();

        boolean isAvailable = false;

        if(networkInfo != null && networkInfo.isConnected()){
            isAvailable = true;
        }

        return isAvailable;
    }
    private void populateListView() {
        ArrayAdapter<Post> adapter = new MyListAdapter();
        ListView list = (ListView) findViewById(R.id.postsListView);
        list.setAdapter(adapter);
    }

    private class MyListAdapter extends ArrayAdapter<Post>{
        public MyListAdapter() {
            super(MainActivity.this, R.layout.item_view, myPosts);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            // make sure we have a view to work with
            View itemView = convertView;
            if (itemView == null) {
                itemView = getLayoutInflater().inflate(R.layout.item_view, parent,false);
            }
            //find the post to work with
            Post currentPost = myPosts.get(position);
            Context context = itemView.getContext();

            String imageURL = currentPost.getImage();
            if(imageURL == null || imageURL.isEmpty()){
                ImageView imageView = (ImageView) itemView.findViewById(R.id.item_image);
                imageView.setVisibility(View.GONE);
            }else{
                ImageView imageView = (ImageView) itemView.findViewById(R.id.item_image);
                Picasso.with(context)
                        .load(imageURL)
                        .tag(context)
                        .placeholder(R.drawable.kanye8080s)
                        .error(R.drawable.stadiumarcadium)
                        .into(imageView);
                imageView.setVisibility(View.VISIBLE);
            }

            //Username
            TextView userText = (TextView) itemView.findViewById(R.id.item_txtUser);
            userText.setText(currentPost.getUser());

            //Time of post
            TextView timeText = (TextView) itemView.findViewById(R.id.item_txtTime);
            timeText.setText("" + currentPost.getTime());

            //The actual post
            TextView postText = (TextView) itemView.findViewById(R.id.item_txtPost);
            postText.setText("" + currentPost.getPost());

            //The actual post
            TextView likesText = (TextView) itemView.findViewById(R.id.item_txtLikes);
            likesText.setText("" + currentPost.getLikes());

            return itemView;
        }
    }

    private class GetBlogPostsTask extends AsyncTask<Object, Void, List> {

        @Override
        protected List doInBackground(Object[] params) {

            int responseCode = -1;//need to have this variable outside scope of try/catch block
            JSONObject jsonResponse = null;
            StringBuilder builder = new StringBuilder();
            HttpClient client = new DefaultHttpClient();
            HttpGet httpget = new HttpGet(""); /// api endpoint redacted

            try {

                HttpResponse response = client.execute(httpget);
                StatusLine statusLine = response.getStatusLine();
                responseCode = statusLine.getStatusCode();

                if(responseCode == HttpURLConnection.HTTP_OK){ //could have used just 200 value
                    HttpEntity entity = response.getEntity();
                    InputStream content = entity.getContent();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
                    String line;
                    while((line = reader.readLine()) != null){
                        builder.append(line);
                    }

                    jsonResponse = new JSONObject(builder.toString());

                    JSONArray jsonPosts = jsonResponse.getJSONArray("posts");
                    for(int i=0; i < jsonPosts.length(); i++ ){
                        JSONObject jsonPost = jsonPosts.getJSONObject(i);

                        int post_id = Integer.parseInt(jsonPost.getString("id"));
                        String post_user = jsonPost.getString("user");
                        String post_account = jsonPost.getString("account");
                        int post_time = Integer.parseInt(jsonPost.getString("time"));
                        String post_post = jsonPost.getString("post");
                        String post_image = jsonPost.getString("image");
                        int post_likes = Integer.parseInt(jsonPost.getString("likes"));

                        myPosts.add(new Post(post_id, post_user, post_account, post_time, post_post, post_image, "profile picture here", post_likes));
                    }
                }else{
                    Log.i(TAG, "Unsuccessful HTTP Response Code: " + responseCode);
                }
            }
            catch (MalformedURLException e){
                Log.e(TAG, "Exception caught");
            }
            catch (IOException e){
                Log.e(TAG, "Exception caught");
            }
            catch (Exception e){//must be in this order, this is the last, general catch
                Log.e(TAG, "Exception caught", e);
            }

            return null;
        }
        @Override
        protected void onPostExecute(List result) {
            // call populateListView method here
            populateListView();
            super.onPostExecute(result);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

EDIT:

I have updated my code into a view holder pattern, created two separate views (one for a post with an image, one for a post with just text) and also included Picasso's new scroll detection capabilities.

I have seen an improvement in some of the images loading quicker, at least when the view is focused while scrolling, the image is more likely to load now. However, on scroll up those same images that were once loaded, disappear. It feels as if Picasso is only loading 4-5 images at a time and replacing the ones already loaded to make room. My updated code is below:

public class MainActivity extends Activity {
    private List<Post> myPosts = new ArrayList<Post>();
    protected String[] mBlogPostTitles;
    public static final String TAG = MainActivity.class.getSimpleName();//prints name of class without package name

    ...

    private void populateListView() {
        Activity activity = MainActivity.this;

        ArrayAdapter<Post> adapter = new MyListAdapter();
        ListView list = (ListView) findViewById(R.id.postsListView);
        list.setAdapter(adapter);
        list.setOnScrollListener(new SampleScrollListener(activity));
    }

    private class MyListAdapter extends ArrayAdapter<Post>{
        public MyListAdapter() {
            super(MainActivity.this, R.layout.item_view, myPosts);
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            String imageURL = myPosts.get(position).getImage();
            if(imageURL == null || imageURL.isEmpty()){
                return 1; // text based
            }else{
                return 0; // image based
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            PostViewHolder holder;

            int type = getItemViewType(position);

            View itemView = convertView;

            // make sure we have a view to work with
            if (itemView == null) {
                holder = new PostViewHolder();
                if(type == 1) {
                    itemView = getLayoutInflater().inflate(R.layout.item_view, parent, false);
                } else {
                    itemView = getLayoutInflater().inflate(R.layout.image_post_view, parent, false);
                    holder.image = (ImageView) itemView.findViewById(R.id.item_image);
                }
                holder.user = (TextView) itemView.findViewById(R.id.item_txtUser);
                holder.time = (TextView) itemView.findViewById(R.id.item_txtTime);
                holder.post = (TextView) itemView.findViewById(R.id.item_txtPost);
                holder.likes = (TextView) itemView.findViewById(R.id.item_txtLikes);

                itemView.setTag(holder);
            } else {
                holder = (PostViewHolder) itemView.getTag();
            }

            //find the post to work with
            Post currentPost = myPosts.get(position);

            if(type != 1) {
                Context context = itemView.getContext();
                String imageURL = currentPost.getImage();

                Picasso.with(context).setIndicatorsEnabled(true);
                //Picasso.with(context).setLoggingEnabled(true);
                Picasso.with(context)
                        .load(imageURL)
                        .tag(context)
                        .placeholder(R.drawable.kanye8080s)
                        //.skipMemoryCache()
                        .error(R.drawable.stadiumarcadium)
                        .fit()
                        .into(holder.image);
            }
            //Username
            holder.user.setText(currentPost.getUser());

            //Time of post
            holder.time.setText("" + currentPost.getTime());

            //The actual post
            holder.post.setText(currentPost.getPost());

            //Likes for the post
            holder.likes.setText("" + currentPost.getLikes());

            return itemView;
        }
    }
    public class SampleScrollListener implements AbsListView.OnScrollListener {
        private final Context context;

        public SampleScrollListener(Context context) {
            this.context = context;
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            final Picasso picasso = Picasso.with(context);
            if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
                picasso.resumeTag(context);
            } else {
                picasso.pauseTag(context);
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                             int totalItemCount) {
            // Do nothing.
        }
    }
    ...
}

Where is the issue coming from? Should I be preloading these images somehow in the cache? While I have already looked into Picasso's new priority feature, should I be telling Picasso somehow to load images in the order in which they appear in my listview? Any ideas? How can I "keep" images that have already been loaded on scroll up?

Answer

Pb Studies picture Pb Studies · Jun 20, 2015

use resize with picasso

 Picasso.with(context)
.load(imageURL)
.tag(context)
.placeholder(R.drawable.kanye8080s)
.error(R.drawable.stadiumarcadium)
.into(imageView)
.resize(x,y);

//This would definitely help