title: Android Picasso Recyclerview Image downloading slow
body: <p>I have endless recyclerview that almost mimics Instagram list/feeds. Where Image is loading into full screen width Imageview. I am using Picasso to load images. Here is my code:</p>
  
  <pre><code>public class HomeAdapter extends RecyclerView.Adapter {
  
  Context context;
  
  private Contract contract;
  
  List&lt;Feed&gt; feeds;
  
  static final int ITEM_TYPE_HEADER = 0;
  
  static final int ITEM_TYPE_LOAD_MORE = 1;
  
  static final int ITEM_TYPE_DESIGN = 2;
  
  public HomeAdapter(Context context, List&lt;Feed&gt; feeds) {
      this.context = context;
      this.feeds = feeds;
  }
  
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      LayoutInflater inflater = LayoutInflater.from(parent.getContext());
      View row;
      if (viewType == ITEM_TYPE_HEADER) {
          row = inflater.inflate(R.layout.list_item_home_header, parent, false);
          return new HeaderHolder(row);
      } else if (viewType == ITEM_TYPE_LOAD_MORE) {
          row = inflater.inflate(R.layout.list_item_load_more, parent, false);
          return new LoadMoreHolder(row);
      } else if (viewType == ITEM_TYPE_DESIGN) {
          row = inflater.inflate(R.layout.list_item_feed_home, parent, false);
          return new DesignItemHolder(row);
      }
      return null;
  }
  
  @Override
  public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
      Feed feed = feeds.get(position);
      if (holder instanceof HeaderHolder) {
          HeaderHolder headerHolder = (HeaderHolder) holder;
          Header header = (Header) feed;
          headerHolder.tvTitle.setText(header.getTitle());
          headerHolder.tvDescription.setText(header.getDescription());
  
      } else if (holder instanceof LoadMoreHolder) {
          LoadMoreHolder loadMoreHolder = (LoadMoreHolder) holder;
          LoadMore loadMore = (LoadMore) feed;
          if(loadMore.getType() == LOAD_MORE_TYPE_PROGRESS) {
              loadMoreHolder.pbLoadMore.setVisibility(View.VISIBLE);
              loadMoreHolder.rlMessageRetry.setVisibility(View.GONE);
          } else {
              loadMoreHolder.pbLoadMore.setVisibility(View.GONE);
              loadMoreHolder.rlMessageRetry.setVisibility(View.VISIBLE);
              if(StringUtils.isValid(loadMore.getTitle())) {
                  loadMoreHolder.tvTitle.setText(loadMore.getTitle());
                  loadMoreHolder.tvTitle.setVisibility(View.VISIBLE);
              } else
                  loadMoreHolder.tvTitle.setVisibility(View.GONE);
  
              if(StringUtils.isValid(loadMore.getDescription())) {
                  loadMoreHolder.tvDescription.setText(loadMore.getDescription());
                  loadMoreHolder.tvDescription.setVisibility(View.VISIBLE);
              } else
                  loadMoreHolder.tvDescription.setVisibility(View.GONE);
          }
  
          loadMoreHolder.btnRetry.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onRetryLoadMoreClick();
              }
          });
  
      } else if (holder instanceof DesignItemHolder) {
          final DesignItemHolder designItemHolder = (DesignItemHolder) holder;
          final DesignFeed designFeed = (DesignFeed) feed;
          String title = getFeedTitle(designFeed.getDesignCategory().getTitle(), designFeed.getEvent().getTitle());
          designItemHolder.tvCategory.setText(StringUtils.toUpperCaseSentence(designFeed.getDesignCategory().getTitle()));
          designItemHolder.tvDate.setText(DateTimeUtils.formatTime(designFeed.getDate()));
          designItemHolder.tvTitle.setText(StringUtils.toUpperCaseSentence(title));
          designItemHolder.tvBrand.setText(getBrandTitle(designFeed.getBrand().getTitle()));
  
          if(StringUtils.isValid(designFeed.getShopLink())) {
              designItemHolder.tvShop.setVisibility(View.VISIBLE);
              if(StringUtils.isValid(designFeed.getPrice())) {
                  designItemHolder.tvPrice.setVisibility(View.VISIBLE);
                  designItemHolder.tvPrice.setText(designFeed.getPrice() + " PKR");
              } else
                  designItemHolder.tvPrice.setVisibility(View.GONE);
  
              designItemHolder.tvShop.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      if(contract != null)
                          contract.onOpenShopLink(designFeed.getShopLink());
                  }
              });
          }
          else {
              designItemHolder.tvPrice.setVisibility(View.GONE);
              designItemHolder.tvShop.setVisibility(View.GONE);
          }
  
          showFeedImage(designFeed, designItemHolder.ivThumbnail, designItemHolder.progressBar);
  
          Picasso.get()
                  .load(designFeed.getDesignCategory().getThumbnail())
                  .transform(new CircleTransform())
                  .placeholder(new ColorDrawable(ContextCompat.getColor(context, R.color.colorGreyLight)))
                  .into(designItemHolder.ivCategory);
  
          designItemHolder.ivThumbnail.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onDesignFeedClick(designFeed);
              }
          });
  
          designItemHolder.tvCategory.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onDesignCategoryClick(designFeed.getDesignCategory());
              }
          });
  
          designItemHolder.ivCategory.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onDesignCategoryClick(designFeed.getDesignCategory());
              }
          });
  
          designItemHolder.tvTitle.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onDesignCategoryClick(designFeed.getDesignCategory());
              }
          });
  
          designItemHolder.tvBrand.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onBrandClick(designFeed.getBrand());
              }
          });
  
          designItemHolder.ibFavourite.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onFavouriteClick(designFeed);
              }
          });
  
          designItemHolder.ibShare.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onShareClick(designFeed);
              }
          });
  
          designItemHolder.ibDownload.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  if(contract != null)
                      contract.onDownloadClick(designFeed);
              }
          });
      }
  }
  
  private void showFeedImage(final DesignFeed designFeed, final ImageView ivThumbnail, final ProgressBar progressBar) {
      Picasso.get()
              .load(designFeed.getThumbnail())
              .placeholder(new ColorDrawable(ContextCompat.getColor(context, R.color.colorGreyLight)))
              .networkPolicy(NetworkPolicy.OFFLINE)
              .into(ivThumbnail, new Callback() {
                  @Override
                  public void onSuccess() {
                  }
  
                  @Override
                  public void onError(Exception e) {
                      //Try again online if cache failed
                      Picasso.get()
                              .load(designFeed.getThumbnail())
                              .placeholder(new ColorDrawable(ContextCompat.getColor(context, R.color.colorGreyLight)))
                              .into(ivThumbnail, new Callback() {
                                  @Override
                                  public void onSuccess() {
                                  }
  
                                  @Override
                                  public void onError(Exception e) {
  
                                  }
                              });
                  }
              });
  }
  
  @Override
  public int getItemViewType(int position) {
      Feed feed = feeds.get(position);
      if (feed instanceof Header)
          return ITEM_TYPE_HEADER;
      else if (feed instanceof LoadMore)
          return ITEM_TYPE_LOAD_MORE;
      else if (feed instanceof DesignFeed)
          return ITEM_TYPE_DESIGN;
      else
          return super.getItemViewType(position);
  }
  
  @Override
  public int getItemCount() {
      return feeds.size();
  }
  
  public void insertFeedAtStart(Feed feed) {
      feeds.add(0, feed);
      notifyDataSetChanged();
  }
  
  public void updateFeeds(ArrayList&lt;Feed&gt; mFeeds) {
      feeds.addAll(mFeeds);
      notifyDataSetChanged();
  }
  
  public void addLoadMoreItem(Feed loadMoreFeed) {
      feeds.add(loadMoreFeed);
      notifyItemInserted(getItemCount() - 1);
  }
  
  public void removeItem(int index) {
      feeds.remove(index);
      notifyDataSetChanged();
  }
  
  private class DesignItemHolder extends RecyclerView.ViewHolder {
      TextView tvTitle, tvCategory, tvBrand, tvDate, tvPrice, tvShop;
      ImageView ivCategory, ivThumbnail;
      ImageButton ibFavourite, ibShare, ibDownload;
      ProgressBar progressBar;
  
      DesignItemHolder(View itemView) {
          super(itemView);
          tvTitle = itemView.findViewById(R.id.tvTitle);
          tvCategory = itemView.findViewById(R.id.tvCategory);
          tvBrand = itemView.findViewById(R.id.tvBrand);
          tvDate = itemView.findViewById(R.id.tvDate);
          ivCategory = itemView.findViewById(R.id.ivCategory);
          ivThumbnail = itemView.findViewById(R.id.ivThumbnail);
          tvPrice = itemView.findViewById(R.id.tvPrice);
          tvShop = itemView.findViewById(R.id.tvShop);
          ibFavourite = itemView.findViewById(R.id.ibFavourite);
          ibShare = itemView.findViewById(R.id.ibShare);
          ibDownload = itemView.findViewById(R.id.ibDownload);
          progressBar = itemView.findViewById(R.id.progressBar);
      }
  }
  
  private class HeaderHolder extends RecyclerView.ViewHolder {
      TextView tvTitle, tvDescription;
      HeaderHolder(View itemView) {
          super(itemView);
          tvTitle = itemView.findViewById(R.id.tvTitle);
          tvDescription = itemView.findViewById(R.id.tvDescription);
      }
  }
  
  private class LoadMoreHolder extends RecyclerView.ViewHolder {
      ProgressBar pbLoadMore;
      RelativeLayout rlMessageRetry;
      TextView tvTitle, tvDescription;
      AppCompatButton btnRetry;
  
      LoadMoreHolder(View itemView) {
          super(itemView);
          pbLoadMore = itemView.findViewById(R.id.pbLoadMore);
          rlMessageRetry = itemView.findViewById(R.id.rlMessageRetry);
          tvTitle = itemView.findViewById(R.id.tvTitle);
          tvDescription = itemView.findViewById(R.id.tvDescription);
          btnRetry = itemView.findViewById(R.id.btnRetry);
      }
  }
  
  public void setContract(Contract contract) {
      this.contract = contract;
  }
  
  public interface Contract {
  
      void onDesignCategoryClick(DesignCategory designCategory);
  
      void onDesignFeedClick(DesignFeed designCategory);
  
      void onBrandClick(Brand brand);
  
      void onRetryLoadMoreClick();
  
      void onShopClick(String shopLink);
  
      void onFavouriteClick(DesignFeed designFeed);
  
      void onShareClick(DesignFeed designFeed);
  
      void onDownloadClick(DesignFeed designFeed);
  
      void onOpenShopLink(String shopLink);
  }
  </code></pre>
  
  <p>}</p>
  
  <p>Average image size is 80-100 kbs. But image download time is too slow. It almost takes 3-4 seconds to download an image. On the other hand when images are loading from cache it obviously loads an image in no time. I get 15 items from server to load into recyclerview which means 15 images would be downloading in parallel. But still it is quite slow as compared to Instagram or Facebook feeds.</p>
  
  <p>Can anyone guide me about the correct technique to make it as fast as possible, so that when user scrolls down the list images should be already loaded instead him waiting for image to load?</p>