title: Picasso keeps reloading images while scrolling upwards in listview, loads slowly
body: <p>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.</p>
  
  <p>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?</p>
  
  <pre><code>public class MainActivity extends Activity {
      private List&lt;Post&gt; myPosts = new ArrayList&lt;Post&gt;();
      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 &amp;&amp; networkInfo.isConnected()){
              isAvailable = true;
          }
  
          return isAvailable;
      }
      private void populateListView() {
          ArrayAdapter&lt;Post&gt; adapter = new MyListAdapter();
          ListView list = (ListView) findViewById(R.id.postsListView);
          list.setAdapter(adapter);
      }
  
      private class MyListAdapter extends ArrayAdapter&lt;Post&gt;{
          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&lt;Object, Void, List&gt; {
  
          @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 &lt; 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);
      }
  }
  </code></pre>
  
  <p><strong>EDIT:</strong></p>
  
  <p>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 <a href="http://lucasr.org/2014/09/23/new-features-in-picasso/" rel="nofollow noreferrer">scroll detection capabilities</a>.  </p>
  
  <p>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: </p>
  
  <pre><code>public class MainActivity extends Activity {
      private List&lt;Post&gt; myPosts = new ArrayList&lt;Post&gt;();
      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&lt;Post&gt; adapter = new MyListAdapter();
          ListView list = (ListView) findViewById(R.id.postsListView);
          list.setAdapter(adapter);
          list.setOnScrollListener(new SampleScrollListener(activity));
      }
  
      private class MyListAdapter extends ArrayAdapter&lt;Post&gt;{
          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.
          }
      }
      ...
  }
  </code></pre>
  
  <p>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?</p>