Search code examples
androidandroid-recyclerviewnotifydatasetchangedfresco

New images not displaying after calling notifyDataSetChanged() on Fresco


I've been trying to implement something on my android app for days, but with no success.

I have a recycler view that displays products. The products are wrapped in a cardView, and a gridLayoutManager handles the layout for me. The cardView contains a textView (name of the product) and a simpleDraweeView (image of the product).

I successfully managed to add a SwipeRefreshLayout in which when a user swipes down, the products refresh and new content is added if it exists. The textView updates just fine on refresh. However, the simpleDraweeView does not display the image of the new product until I scroll down and back up again.

I researched the issue and came across this: https://github.com/facebook/fresco/issues/687. But the post did not fully indicate how to go about setting things right.

Below are my code snippets:

Refresh Snippet (MainActivityFragment Class)

//set refresh more listener for the RecyclerView adapter
mainActivityAdapter.setOnRefreshMoreListener(new OnRefreshMoreListener() {
   @Override
    public void onRefreshMore() {
         swipeRefreshLayout.setRefreshing(true);
         new Handler().postDelayed(new Runnable() {
             @Override
             public void run() {
                 productsArrayList.clear();  //I'm removing previous data
                 mainActivityAdapter.notifyDataSetChanged();
                 fetchOnLoad(); //Function that adds new elements to the productsArrayList
                 mainActivityAdapter.notifyDataSetChanged();
                 swipeRefreshLayout.setRefreshing(false);
              }
         }, 1000);
     }
 });

onBindView Snippet (MainActivityAdapter)

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
  if (holder instanceof ProductViewHolder) {
    final Product product = mProducts.get(position);

    Uri uri = Uri.parse(product.getProductImage());
    final ProductViewHolder productViewHolder = (ProductViewHolder) holder;
    productViewHolder.productName.setText(product.getProductName());
    productViewHolder.cardImage.setImageURI(uri);

  }
}

Just like in the link I posted above, I too think that Fresco is not displaying the newly loaded image because onBindViewHolder has not been called again. The image always appears after I scroll out of view and into view again. Is it possible for me to set the data in ViewHolder as he had resolved? If so, how? Any help will be highly appreciated. Thanks

EDIT: As requested, here is the code for fetchOnLoad

public void fetchOnLoad() {

    // showing refresh animation before making http call
    swipeRefreshLayout.setRefreshing(true);

    // appending offset to url
    String url = "https://www.example.com/spilljson.php";

    // Volley's json array request object
    JsonArrayRequest req = new JsonArrayRequest(url,
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    Log.d(TAG, response.toString());

                    if (response.length() > 0) {
                        // looping through json and adding to product list
                        for (int i = 0; i < response.length(); i++) {
                            try {
                                JSONObject productObj = response.getJSONObject(i);

                                String image = productObj.getString("productImage");
                                String name = productObj.getString("productName");

                                Product product = new Product();
                                product.setProductName(name); //This works fine. New product names always shown
                                product.setProductImage(image); //This is where I get the issue. New product images are not shown.
                                products.add(product); //Add the individual product to the products arrayList


                            } catch (JSONException e) {
                                Log.e(TAG, "JSON Parsing error: " + e.getMessage());
                            }
                        }
                        mainActivityAdapter.notifyDataSetChanged();
                    }

                    // stopping swipe refresh
                    swipeRefreshLayout.setRefreshing(false);

                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e(TAG, "Server Error: " + error.getMessage());

            Toast.makeText(mActivity, "We can't access our servers :-(", Toast.LENGTH_LONG).show();

            // stopping swipe refresh
            swipeRefreshLayout.setRefreshing(false);
        }
    });

    // Adding request to request queue
    FrescoApplication.getInstance().addToRequestQueue(req);
}

SCREENSHOT OF ISSUE Here is the link to the screenshot: https://i.sstatic.net/NloHd.jpg

Full MainActivityAdapter

public class MainActivityAdapter
    extends RecyclerView.Adapter<MainActivityAdapter.ViewHolder> {

private RecyclerView mRecyclerView;

private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
private OnLoadMoreListener onLoadMoreListener;
private OnRefreshMoreListener onRefreshMoreListener;
private boolean isLoading;
private List<Product> mProducts;
private SwipeRefreshLayout mSwipeRefresh;
private int visibleThreshold = 5;
private int lastVisibleItem, totalItemCount;
public static final String STRING_IMAGE_URI = "ImageUri";
public static final String STRING_IMAGE_POSITION = "ImagePosition";
private static MainActivity mActivity;

public MainActivityAdapter(RecyclerView recyclerView, SwipeRefreshLayout refreshLayout, List<Profile> products, MainActivity myContext) {
    mRecyclerView = recyclerView;
    mProducts = products;
    mActivity = myContext;
    mSwipeRefresh = refreshLayout;

    final GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            totalItemCount = gridLayoutManager.getItemCount();
            lastVisibleItem = gridLayoutManager.findLastVisibleItemPosition();
            if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                if (onLoadMoreListener != null) {
                    onLoadMoreListener.onLoadMore();
                }
                isLoading = true;
            }
        }
    });

    mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            if (onRefreshMoreListener != null) {
                onRefreshMoreListener.onRefreshMore();
            }
        }
    });
}

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

public void setOnRefreshMoreListener(OnRefreshMoreListener mOnRefreshMoreListener) {
    this.onRefreshMoreListener = mOnRefreshMoreListener;
}

public static class ViewHolder extends RecyclerView.ViewHolder {
    public final View mView;
    public final SimpleDraweeView mImageView;
    public final RelativeLayout mLayoutItem;

    public ViewHolder(View view) {
        super(view);
        mView = view;
        mImageView = (SimpleDraweeView) view.findViewById(R.id.user_image_listing);
        mLayoutItem = (RelativeLayout) view.findViewById(R.id.layout_item);
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == VIEW_TYPE_ITEM) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_layout_profile, parent, false);
        return new ProductViewHolder(view);
    } else if (viewType == VIEW_TYPE_LOADING) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, null, false);
        return new LoadingViewHolder(view);
    }
    return null;
}

@Override
public void onViewRecycled(ViewHolder holder) {
    if (holder instanceof ProductViewHolder) {
        ProductViewHolder productViewHolder = (ProductViewHolder) holder;

        if (productViewHolder.mImageView.getController() != null) {
            productViewHolder.mImageView.getController().onDetach();
        }
        if (productViewHolder.mImageView.getController() != null) {
            productViewHolder.mImageView.getTopLevelDrawable().setCallback(null);
        }
    }
}

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
  if (holder instanceof ProductViewHolder) {
    final Product product = mProducts.get(position);

    Uri uri = Uri.parse(product.getProductImage());
    final ProductViewHolder productViewHolder = (ProductViewHolder) holder;
    productViewHolder.productName.setText(product.getProductName());
    productViewHolder.cardImage.setImageURI(uri);

  }
}

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

public void setLoaded() {
    isLoading = false;
}

@Override
public int getItemViewType(int position) {
    return mProducts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
}

private class LoadingViewHolder extends ViewHolder {
    public ProgressBar progressBar;

    public LoadingViewHolder(View view) {
        super(view);
        progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
    }
}

private class ProductViewHolder extends ViewHolder {
    public TextView productName;
    public SimpleDraweeView cardImage;

    public ProductViewHolder(View view) {
        super(view);
        productName = (TextView) view.findViewById(R.id.txt_product_name);
        cardImage = (SimpleDraweeView) view.findViewById(R.id.product_image);
    }
}
}

Full MainActivityFragment

public class MainActivityFragment extends Fragment {

public static final String STRING_IMAGE_URI = "ImageUri";
public static int SPAN_SIZE = 2;
public static final String STRING_IMAGE_POSITION = "ImagePosition";
public static final String STRING_PRODUCT_NAME = "Name";
private static MainActivity mActivity;
private List<Product> productsArrayList;
private MainActivityAdapter mainActivityAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private Connectivity connectivity; //Simple class I created to check when internet connection is available or not. Works fine.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mActivity = (MainActivity) getActivity();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    swipeRefreshLayout = (SwipeRefreshLayout) inflater.inflate(R.layout.main_layout_recyclerview, container, false);
    setupRecyclerView(swipeRefreshLayout);
    return swipeRefreshLayout;
}

private void setupRecyclerView(final SwipeRefreshLayout swipeRefreshLayout) {
    RecyclerView recyclerView = (RecyclerView) swipeRefreshLayout.findViewById(R.id.recyclerview);

    productsArrayList = new ArrayList<>();

    connectivity = new Connectivity(mActivity);

    //The same data is being displayed across the 3 viewpagers I have for now...
    if (MainActivityFragment.this.getArguments().getInt("type") == 1) {
        SPAN_SIZE = 2;
    } else if (MainActivityFragment.this.getArguments().getInt("type") == 2) {
        SPAN_SIZE = 2;
    } else if (MainActivityFragment.this.getArguments().getInt("type") == 3) {
        SPAN_SIZE = 1;
    }

    //If internet connection is found... fetch content
    if(connectivity.isInternetAvailable()){
        //This fetches the data from the internet and adds it to the products ArrayList declared above.
        fetchOnLoad();
    } else{
        Toast.makeText(mActivity, "No Internet", Toast.LENGTH_SHORT).show();
    }


    //If products were added to the ArrayList, proceed
    if (productsArrayList != null) {
        GridLayoutManager layoutManager = new GridLayoutManager(mActivity,SPAN_SIZE,GridLayoutManager.VERTICAL,false);
        recyclerView.setLayoutManager(layoutManager);
        mainActivityAdapter = new MainActivityAdapter(recyclerView, swipeRefreshLayout, productsArrayList, mActivity);
        recyclerView.setAdapter(mainActivityAdapter);

        swipeRefreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorAccent));


        //Load more listener for the RecyclerView adapter - working just fine. However, I fear it
        //will have the same problem when I start introducing new products. NB: The JSON that is on my server has static information.
        //This limits the number of visible products to 40 for now as I am reusing data from the
        //products ArrayList.
        mainActivityAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if (productsArrayList.size() <= 40) {
                    swipeRefreshLayout.setRefreshing(true);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            fetchOnLoad();
                            mainActivityAdapter.notifyDataSetChanged();
                            mainActivityAdapter.setLoaded();
                            swipeRefreshLayout.setRefreshing(false);

                        }
                    }, 1500);
                } else {
                    Toast.makeText(mActivity, "More products coming soon", Toast.LENGTH_SHORT).show();
                }
            }
        });

        //Code for this provided above
        mainActivityAdapter.setOnRefreshMoreListener(new OnRefreshMoreListener() {});
    }
}
//Code for this provided above
public void fetchOnLoad() {}

}

Solution

  • I found a solution to my problem which I thought I could share. The problem that I had with using Fresco is that when notifyDataSetChanged() was called, it would load the images just fine, but it would not render them. That way, I was left with a placeholder each time. However, upon scrolling out of view then back again, the image would be shown. I did not like that.

    I tried implementing the solution provided here https://github.com/facebook/fresco/issues/687 by trying to cache the SimpleDraweeView in the viewholder, but that did not help in my case. I used the explanation from this link https://teamtreehouse.com/community/now-lets-work-on-oncreateviewholder-i-started-it-for-youall-you-need-to-do-is-to-create-a to get an idea of how to do that.

    My solution was to switch from Fresco to Glide, and Glide happened to be faster and more responsive than Fresco in my own opinion. This simple code below solved it all...

    Glide.with(mActivity)
         .load(profile.getUserImage())
         .placeholder(R.color.stay_color)
         .into(mImageView);