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() {}
}
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);