I am facing the outOfMemory exception while loading images with Picasso.
I am creating picasso builder with OkHttp and created Picasso Singleton class to cache images.
Scenario:
I have 100+ image feeds which are to be loaded. I am getting images in sets and each set has 25 image url which i am setting with Picasso. I am using recyclerview and whenever new set of images come in i am calling adapter.notifyDataSetChanged(). I am loading first set - no issues, for 2nd and 3rd set when making retrofit to get next set and adding to existing list and calling - adapter.notifyDataSetChanged(). For 3rd set when I call adapter.NotifyDataSetChanged() app crashes with outOfMemoryException
BUT
When i load all 3 sets of 75 images, I don't face any issues.
Code:
Application Class - where I am building Picasso.
Picasso.Builder builder = new Picasso.Builder(this)
.memoryCache(new LruCache(24000));
builder.downloader(new OkHttpDownloader(this,Integer.MAX_VALUE));
Picasso built = builder.build();
built.setLoggingEnabled(true);
Picasso Singleton Class:
public class PicassoCache {
/**
* Static Picasso Instance
*/
private static Picasso picassoInstance = null;
/**
* PicassoCache Constructor
*
* #param context application Context
*/
private PicassoCache (Context context) {
Downloader downloader = new OkHttpDownloader(context, Integer.MAX_VALUE);
Picasso.Builder builder = new Picasso.Builder(context);
builder.downloader(downloader);
picassoInstance = builder.build();
}
/**
* Get Singleton Picasso Instance
*
* #param context application Context
* #return Picasso instance
*/
public static Picasso getPicassoInstance (Context context) {
if (picassoInstance == null) {
new PicassoCache(context);
return picassoInstance;
}
return picassoInstance;
}
}
Code where I am setting/loading images with Picasso.
PicassoCache.getPicassoInstance(context).load(url).placeholder(R.mipmap.banner_placeholder).into(mView);
Code where I am updating the existing list to load when there is data set changed.
Inside Retorfit OnSuccess message:
if (response.code() == 200) {
List<CampaignCard> newCampaigns = response.body().getCampaigns();
for (int i = 0; i < newCampaigns.size(); i++) {
if (!campaignCards.contains(newCampaigns.get(i))) {
campaignCards.add(newCampaigns.get(i));
}
}
dashBoardAdapter.notifyDataSetChanged();
} else if (response.code() == Params.CODE_422) {
Utils.ShowServiceErrorMessages(getActivity(), response);
} else if (response.code() == Params.CODE_401) {
Utils.Logout(getActivity());
}
Adapter Class:
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardViewHolder> implements View.OnClickListener {
private static final int VIEW_TYPE_CAMPAIGN = 1;
private static final int VIEW_TYPE_FEED = 2;
DashboardViewHolder holder;
protected List list;
protected int viewTypeLayout;
Context context;
int position;
Dashboard.DashboardActionList actionList;
Map<String, SourceContent> mPreviewLinkMapper;
ViewGroup parent;
//Picasso p;
public DashboardAdapter(List list, int viewTypeLayout) {
this.list = list;
this.viewTypeLayout = viewTypeLayout;
}
public DashboardAdapter(List list, int viewTypeLayout, Context context, Map<String, SourceContent> mPreviewLinkMapper) {
this.list = list;
this.viewTypeLayout = viewTypeLayout;
this.mPreviewLinkMapper = mPreviewLinkMapper;
}
#Override
public DashboardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_feed, parent, false);
return new DashboardViewHolder(view, 2);
}
#Override
public void onBindViewHolder(DashboardViewHolder holder, final int position) {
BindFeedData(holder, position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemCount() {
return list.size();
}
#SuppressWarnings("unchecked")
private void BindFeedData(DashboardViewHolder holder, int position) {
List<Feed> feeds = (List<Feed>) list;
if (holder.mBannerImage != null) {
if (feeds.get(position).getCampaign().getParticipation().getType().toLowerCase().contains("video")) {
holder.mPlayIcon.setVisibility(View.VISIBLE);
String url = feeds.get(position).getCampaign().getParticipation().getThumbnail_url();
Utils.LoadImages(context, url, holder.mBannerImage, false);
} else if (feeds.get(position).getCampaign().getParticipation().getType().toLowerCase().contains("gif")) {
holder.mPlayIcon.setVisibility(View.GONE);
String url = feeds.get(position).getCampaign().getParticipation().getPost_file();
Utils.loadGif(context, url, holder.mBannerImage);
} else if (feeds.get(position).getCampaign().getParticipation().getType().toLowerCase().contains("link") ||
feeds.get(position).getCampaign().getParticipation().getType().toLowerCase().contains("youtube")) {
holder.mPlayIcon.setVisibility(View.GONE);
String slug = feeds.get(position).getCampaign().getSlug();
List<String> images = mPreviewLinkMapper.get(slug).getImages();
Utils.LoadImages(context, images.get(0), holder.mBannerImage, false);
} else {
holder.mPlayIcon.setVisibility(View.GONE);
holder.mBannerImage.setVisibility(View.VISIBLE);
String url = feeds.get(position).getCampaign().getParticipation().getPost_file();
Utils.LoadImages(context, url, holder.mBannerImage, false);
}
}
if (holder.mBrandLogo != null) {
Utils.LoadImages(context, feeds.get(position).getInfluencer().getProfile_picture_url(), holder.mBrandLogo, true);
}
holder.mTitle.setText(feeds.get(position).getInfluencer().getName());
holder.mSubTitle.setText(feeds.get(position).getCampaign().getName());
holder.mTime.setText(feeds.get(position).getCampaign().getTimestamp());
holder.mDescription.setText(feeds.get(position).getCampaign().getParticipation().getPost_content());
holder.mEngagement.setText(feeds.get(position).getCampaign().getParticipation().getMetrics().getEngagements());
holder.mImpresion.setText(feeds.get(position).getCampaign().getParticipation().getMetrics().getImpressions());
}
}
public static class DashboardViewHolder extends RecyclerView.ViewHolder {
ImageView mBannerImage, mFacebook, mTwitter, mInstagram, mPlayIcon, mHotIcon, mLocationIcon;
CircularImageView mBrandLogo;
CustomTextViewRegular mDescription, mTime, mOption1, mOption2, mOption3;
CustomTextViewDemi mTitle, mSubTitle, mImpresion, mEngagement;
LinearLayout mDetailsLayout;
LinearLayout mOptionLayout1, mOptionLayout2, mOptionLayout3;
public ViewGroup dropPreview;
TableRow mTableOptions;
public DashboardViewHolder(View v, int viewtype) {
super(v);
InitFeedViews(v);
}
private void InitFeedViews(View v) {
mTitle = (CustomTextViewDemi) v.findViewById(R.id.adapterHeaderLayoutTitle);
mSubTitle = (CustomTextViewDemi) v.findViewById(R.id.adapterHeaderLayoutSubTitle);
mBrandLogo = (CircularImageView) v.findViewById(R.id.adapterHeaderLayoutLogo);
mTime = (CustomTextViewRegular) v.findViewById(R.id.adapterHeaderLayoutTime);
mBannerImage = (ImageView) v.findViewById(R.id.adapterFeedBannerImage);
mPlayIcon = (ImageView) v.findViewById(R.id.adapterFeedPlayIocn);
mImpresion = (CustomTextViewDemi) v.findViewById(R.id.adapterFeedImpressions);
mEngagement = (CustomTextViewDemi) v.findViewById(R.id.adapterFeedEngagements);
mDescription = (CustomTextViewRegular) v.findViewById(R.id.adapterFeedDescription);
dropPreview = (LinearLayout) v.findViewById(R.id.drop_preview);
}
}
}
You would face "out of memory" issues lot in case of picasso library I would suggest you to use Glide library.
I had lot "out of memory" exception tried all but at last when i used glide it worked well.
Try to resize the image with Picasso library. OutOfMemoryException will be resolved.
For better understanding, refer below link:
https://futurestud.io/tutorials/picasso-image-resizing-scaling-and-fit
Related
The problem is that in my tablayout when im switching between tabs my list duplicating. So i need to remove list on onStop() to recreate it then. Or might be other better solution.
I have tried the following solutions
https://code-examples.net/en/q/1c97047
How to reset recyclerView position item views to original state after refreshing adapter
Remove all items from RecyclerView
My code of adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(Context context) {
this.onlineUsers = new ArrayList<>();
this.context = context;
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
Log.d("testList", "rating " + user.getRating() + " uid " + user.getUid());
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
int initSize = userList.size();
onlineUsers.addAll(userList);
// notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public String getLastItemId() {
return onlineUsers.get(onlineUsers.size() - 1).getUid();
}
public void clearData() {
List<OnlineUser> data = new ArrayList<>();
addUsers(data);
notifyDataSetChanged();
}
My code in fragment
#Override
public void onStop() {
super.onStop();
firstUid = "";
stopDownloadList = false;
List<OnlineUser> list = new ArrayList<>();
mAdapter.addUsers(list);
mAdapter.notifyDataSetChanged();
}
`users are added after callback
#Override
public void addUsers(List<OnlineUser> onlineUsers) {
if (firstUid.equals("")){
firstUid = onlineUsers.get(0).getUid();
}
if (!firstUid.equals("") && onlineUsers.contains(firstUid)){
stopDownloadList = true;
}
if (!stopDownloadList){
mAdapter.addUsers(onlineUsers);
}
setRefreshProgress(false);
isLoading = false;
isMaxData = true;
}
The line mAdapter.addUsers(onlineUsers); from addUsers method gets called twice. Looks like your asynchronous operation gets triggered twice (e. g. from repeating lifecycle methods like onCreate/onCreateView/onViewCreated).
Solution #1: request users a single time
Move your user requesting machinery to onCreate or onAttach. This will save network traffic but could lead to showing outdated data.
Solution #2: replaceUsers
Your clearData calls mAdapter.addUsers(new ArrayList<>()); (btw, take a look at Collections.emptyList()). Looks like you're trying to replace adapter data but appending instead. Replacement method could look like
public void replaceUsers(List<OnlineUser> userList) {
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size);
}
This version still requeses users every time your fragment gets focused but shows fresher data.
I'm beginner in Android development. I'm using this library for http requests https://github.com/loopj/android-async-http.
And then I render response JSON in GridView it takes very long time. I'm using progress dialog as preloader, but it's removed before listview is rendered. How I can solve this problem? Thank you.
Here is screenshot of this GridView.
Here is implementation of this activity.
public class LikesActivity extends BottomBarActivity //some custom activity {
List<Anticafe> mAnticafes = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_likes);
/*some code*/
Request.post(Constants.POST_SEARCH_API_LINK, params, new AsyncHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
Gson gson = new GsonBuilder().create();
Request request = gson.fromJson(new String(responseBody), Request.class);
mAnticafes = request.getAnticafes();
renderListView()
}
#Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
hideProcessDialog();
}
});
}
private void renderListView() {
GridView likesList = (GridView) findViewById(R.id.likes_list);
AnticafeAdapter anticafeAdapter = new AnticafeAdapter(this, mAnticafes);
anticafeAdapter.setWidth(getWidth());
likesList.setAdapter(anticafeAdapter);
}
}
Here is full implementaion of adapter.
public class AnticafeAdapter extends BaseAdapter{
public static final String TAG = "image-request";
private Context mContext;
private List<Anticafe> mAnticafes = new ArrayList<>();
private Client mClient;
private View mConvertView;
private int mWidth;
public AnticafeAdapter(Context context, List<Anticafe> anticafes) {
mContext = context;
mAnticafes = anticafes;
}
public void setWidth(int mWidth) {
this.mWidth = (int) (mWidth / 4.7);
}
#Override
public int getCount() {
return mAnticafes.size();
}
#Override
public Anticafe getItem(int position) {
return mAnticafes.get(position);
}
#Override
public long getItemId(int position) {
return getItem(position).getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final Anticafe anticafe = getItem(position);
mClient = AnticafeApplication.getInstanse().getClient();
mConvertView = convertView;
final ProgressDialog processDialog = new ProgressDialog(mContext);
processDialog.setMessage("Информация обновляется. Пожалуйста, подождите.");
if(mConvertView == null) {
mConvertView = LayoutInflater.from(mContext).inflate(R.layout.adapter_popular, null);
}
ImageView cover = (ImageView) mConvertView.findViewById(R.id.cover);
CircularImageView logo = (CircularImageView) mConvertView.findViewById(R.id.logo);
final ImageView like = (ImageView) mConvertView.findViewById(R.id.like);
final TextView likesCount = (TextView) mConvertView.findViewById(R.id.likes_count);
TextView name = (TextView) mConvertView.findViewById(R.id.name);
TextView price = (TextView) mConvertView.findViewById(R.id.price);
if(mClient != null) {
final boolean liked = mClient.containsLike(anticafe.getId());
if(liked) {
like.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_like));
} else {
like.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_unlike));
}
} else {
like.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_unlike));
}
likesCount.setText(String.valueOf(anticafe.getTotalLikes()));
name.setText(anticafe.getName());
price.setText(anticafe.getPrices());
Picasso.with(mContext).load(anticafe.getAttachment().getCover()).networkPolicy(NetworkPolicy.OFFLINE).into(cover);
Picasso.with(mContext).load(anticafe.getAttachment().getLogo()).resize(mWidth, mWidth).into(logo);
likeMechanism(anticafe, processDialog, like, likesCount);
name.setOnClickListener(anticafeOpenActivityEvent(anticafe.getId()));
price.setOnClickListener(anticafeOpenActivityEvent(anticafe.getId()));
logo.setOnClickListener(anticafeOpenActivityEvent(anticafe.getId()));
cover.setOnClickListener(anticafeOpenActivityEvent(anticafe.getId()));
BottomBarActivity activity = (BottomBarActivity) this.mContext;
activity.hideProcessDialog();
return mConvertView;
}
private void likeMechanism(final Anticafe anticafe, final ProgressDialog processDialog, final ImageView like, final TextView likesCount) {
like.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (mClient == null) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle("Вы не авторизованы")
.setMessage("До тех пор, пока вы не пройдете авторизацию, вы не сможете пользоваться некоторым функционалом нашего приложение. Авторизация займет всего лишь пару минут.")
.setCancelable(false)
.setNegativeButton("Скрыть", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setPositiveButton("Авторизоваться", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(mContext, AuthActivity.class);
mContext.startActivity(intent);
Activity activity = ((Activity) mContext);
activity.overridePendingTransition(R.anim.slide_in_left, R.anim.slide_in_right);
}
});
AlertDialog dialog = builder.create();
dialog.show();
} else {
processDialog.show();
RequestParams params = new RequestParams();
params.add("anticafe_id", anticafe.getId() + "");
AuthedRequest.post(Constants.LIKE_API_LINK, params, new AsyncHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
processDialog.hide();
Gson gson = new GsonBuilder().create();
try {
LikeResponse response = gson.fromJson(new String(responseBody, Constants.UTF_8), LikeResponse.class);
if (response.getLikeStatus().equals("unliked")) {
like.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_unlike));
} else if (response.getLikeStatus().equals("liked")) {
like.setImageDrawable(mContext.getResources().getDrawable(R.drawable.ic_like));
}
likesCount.setText(String.valueOf(response.getTotalLikes()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} finally {
processDialog.hide();
}
}
#Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
try {
Log.d("json", "onSuccess: " + new String(responseBody, Constants.UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} finally {
processDialog.hide();
}
}
});
}
}
});
}
private OnClickListener anticafeOpenActivityEvent(final int id) {
return new OnClickListener() {
#Override
public void onClick(View v) {
Bundle bundle = new Bundle();
bundle.putInt("entity_id", id);
((BottomBarActivity) mContext).openNewActivity(AnticafeActivity.class, bundle);
}
};
}
private static class LikeResponse {
#SerializedName("status")
#Expose
private Integer status;
#SerializedName("error")
#Expose
private Boolean error;
#SerializedName("likeStatus")
#Expose
private String likeStatus;
#SerializedName("totalLikes")
#Expose
private Integer totalLikes;
public LikeResponse(Integer status, Boolean error, String likeStatus, Integer totalLikes) {
this.status = status;
this.error = error;
this.likeStatus = likeStatus;
this.totalLikes = totalLikes;
}
/**
*
* #return
* The status
*/
public Integer getStatus() {
return status;
}
/**
*
* #param status
* The status
*/
public void setStatus(Integer status) {
this.status = status;
}
/**
*
* #return
* The error
*/
public Boolean getError() {
return error;
}
/**
*
* #param error
* The error
*/
public void setError(Boolean error) {
this.error = error;
}
/**
*
* #return
* The likeStatus
*/
public String getLikeStatus() {
return likeStatus;
}
/**
*
* #param likeStatus
* The likeStatus
*/
public void setLikeStatus(String likeStatus) {
this.likeStatus = likeStatus;
}
/**
*
* #return
* The totalLikes
*/
public Integer getTotalLikes() {
return totalLikes;
}
/**
*
* #param totalLikes
* The totalLikes
*/
public void setTotalLikes(Integer totalLikes) {
this.totalLikes = totalLikes;
}
}
}
You dont show any code to say exactly what is you problem.
But any way its better to use a third party library for loading images from web such as picasso:
http://square.github.io/picasso
this library load your images smoothly and mange caches by itself.
UPDATE:
Other option is that you dont use viewHolder design pattern in your getView method in your adapter class.
In your current code you call findViewById method in every cell of your gridView but you must call it only first time.
to resolve this problem you should use viewHolder design pattern.
An exampple of getView method with view Holder :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
/*
* The convertView argument is essentially a "ScrapView" as described is Lucas post
* http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
* It will have a non-null value when ListView is asking you recycle the row layout.
* So, when convertView is not null, you should simply update its contents instead of inflating a new row layout.
*/
if(convertView==null){
// inflate the layout
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
// well set up the ViewHolder
viewHolder = new ViewHolderItem();
viewHolder.textViewItem = (TextView) convertView.findViewById(R.id.textViewItem);
// store the holder with the view.
convertView.setTag(viewHolder);
}else{
// we've just avoided calling findViewById() on resource everytime
// just use the viewHolder
viewHolder = (ViewHolderItem) convertView.getTag();
}
// object item based on the position
ObjectItem objectItem = data[position];
// assign values if the object is not null
if(objectItem != null) {
// get the TextView from the ViewHolder and then set the text (item name) and tag (item ID) values
viewHolder.textViewItem.setText(objectItem.itemName);
viewHolder.textViewItem.setTag(objectItem.itemId);
}
return convertView;
}
Example of View holder class (define your view elements in this class) :
static class ViewHolderItem {
TextView textViewItem;
}
for more info see this :
https://www.javacodegeeks.com/2013/09/android-viewholder-pattern-example.html
I am trying to load images into RecyclerView Adapater I am getting errors with
if(photo!=null) - Unexpected Token.
return newPhotoFilterViewHolder(view); - Invalid Declaration Return Type Required.
public class PhotoFiltersAdapter extends RecyclerView.Adapter - Declare abstract or create abstract method.
Here is my code for the implementation to load Images into RecyclerViewAdapter
public class PhotoFiltersAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private int itemsCount = 12;
ImageView image;
// Fields for the map radius in feet
private float radius = InstaMaterialApplication.getSearchDistance();
/**
* Helper class to get the user location.
*/
private GeoLocationHelper geoLocationHelper = new GeoLocationHelper();
// Adapter for the Parse query
private ParseQueryAdapter<FilterImages> postsQueryAdapter;
public PhotoFiltersAdapter(Context context) {
this.context = context;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(final FilterImages post, ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(context).inflate(R.layout.item_photo_filter, parent, false);
// Set up the query adapter
postsQueryAdapter = new ParseQueryAdapter<FilterImages>(this, factory) {
ParseFile photo = (ParseFile) post.get("image");
if(photo!=null)
{
photo.getDataInBackground(new GetDataCallback() {
#Override
public void done(byte[] data, ParseException e) {
// TODO Auto-generated method stub
if (data != null && e == null) {
Bitmap bitmap = BitmapFactory
.decodeByteArray(data, 0,
data.length);
image.setImageBitmap(bitmap);
} else {
//ParseException e
}
}
});
}
else
{
//picture_not_available
}
TextView contentView = (TextView) view.findViewById(R.id.content_view);
ImageView image=(ImageView)view.findViewById(R.id.picture_view);
TextView usernameView = (TextView) view.findViewById(R.id.username_view);
contentView.setText(post.getText());
return newPhotoFilterViewHolder(view);
};
}
private ParseGeoPoint geoPointFromLocation(Location loc) {
return new ParseGeoPoint(loc.getLatitude(), loc.getLongitude());
}
// Set up a customized query
ParseQueryAdapter.QueryFactory<FilterImages> factory =
new ParseQueryAdapter.QueryFactory<FilterImages>() {
public ParseQuery<FilterImages> create() {
Location myLoc = geoLocationHelper.getCurrentLocation();
ParseQuery query = FilterImages.getQuery();
query.include("PlaceName");
query.orderByDescending("AreaFilters");
query.whereWithinKilometers("AreaFilters", geoPointFromLocation(myLoc), radius);
query.setLimit(itemsCount);
return query;
}
};
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
}
#Override
public int getItemCount() {
return itemsCount;
}
public static class PhotoFilterViewHolder extends RecyclerView.ViewHolder {
public PhotoFilterViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
}
i have use glide to get image from Url, in ParseFile object have method .getUrl()
ParseFile photo = (ParseFile) post.get("image");
Glide.with(getActivity()).load(photo.getUrl()).centerCrop().diskCacheStrategy(DiskCacheStrategy.ALL).into(img_userProfilePicture_bg);
I'm new in Android and I have following code that shows the list of item in Adapter.
I have Four Different Adapter from where I am calling one comman AsyncTask to update Result. I have implemented one Interface ApiResponse and overrides apiResponseProcessing() to get result.
In Item of List "Add to Cart" Button Added in every row. OnClick of that button I am requesting to server. On Success of that response i want to update Button with "Added To Cart".
I have question How to update that string which is binded in onBindViewHolder(). I am getting success in that method but dont know how to update clicked Button from that method.
Here's my Adapter
/**
* Adapter
**/
public class AlbumPhotoDetailAdapter
extends RecyclerView.Adapter<AlbumPhotoDetailAdapter.ViewHolder> implements ApiResponse {
private final ArrayList<Photo> mValues;
Album album;
private Activity mContext;
private int mMemberId;
public AlbumPhotoDetailAdapter(Activity context, ArrayList<Photo> items) {
mValues = items;
this.mContext = context;
mMemberId = MemberPreference.getMemberId(mContext);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.album_photo_detail_sub_view, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
final Photo photo = mValues.get(position);
/**
* Album Owner Name
*/
String mOwnerName = photo.getOwnerName();
String mOwnerProfilePic = photo.getOwnerImage();
String mDateTime = photo.getDatetime();
String mPrice = String.valueOf(photo.getPrice());
/**
* Price String
*/
String priceStr = String.format(mContext.getString(R.string.string_dollar_price), mPrice);
holder.mAlbumPhotoDetailPhotoPrice.setText(priceStr);
/**
* Main Image
*/
Picasso.with(mContext).load(photo.getLink())
.error(R.drawable.ic_place_holder_circle)
.placeholder(R.drawable.ic_place_holder_circle)
.transform(new ImageTransformation(holder.mAlbumPhotoDetailSubMainImage))
.into(holder.mAlbumPhotoDetailSubMainImage);
/**
* Owner Name and Profile Pic
*/
holder.mAlbumPhotoDetailSubOwnerNameTextView.setText(mOwnerName);
Picasso.with(mContext).load(mOwnerProfilePic)
.error(R.drawable.ic_place_holder_circle)
.placeholder(R.drawable.ic_place_holder_circle)
.resize(100, 100)
.transform(new CircleTransform())
.into(holder.mAlbumPhotoDetailSubOwnerImage);
mDateTime = mDateTime != null ? DateUtils.getNiceTime(mDateTime) : "----";
holder.mAlbumPhotoDetailSubOwnerPostedTimeTextView.setText(mDateTime);
// Photo Add to cart.
holder.mAddToCartButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(InternetConnection.checkConnection(mContext)) {
new BackgroundAsyncTask(mContext, (ApiResponse) mContext, mMemberId, photo.getId()).execute();
} else {
DailyStudio.noInternetConnectionToast(mContext);
}
}
});
}
#Override
public int getItemCount() {
return mValues.size();
}
#Override
public void apiResponseProcessing(String response) {
Log.i(TAG,"Api Response : "+response);
if(response.equals(Fields.JSON_SUCCESS)) {
}
}
/**
* View Holder
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
private ImageView mAlbumPhotoDetailSubOwnerImage;
private ImageView mAlbumPhotoDetailSubMainImage;
private TextView mAlbumPhotoDetailSubOwnerNameTextView;
private TextView mAlbumPhotoDetailSubOwnerPostedTimeTextView;
private TextView mAlbumPhotoDetailPhotoPrice;
private TextView mAlbumPhotoDetailSubDescription;
private Button mAddToCartButton;
public ViewHolder(View view) {
super(view);
mView = view;
mAlbumPhotoDetailSubOwnerImage = (ImageView) view.findViewById(R.id.album_photo_detail_sub_owner_image);
mAlbumPhotoDetailSubMainImage = (ImageView) view.findViewById(R.id.album_photo_detail_sub_main_image);
mAlbumPhotoDetailSubOwnerNameTextView = (TextView) view.findViewById(R.id.album_photo_detail_sub_owner_name_text_view);
mAlbumPhotoDetailSubOwnerPostedTimeTextView = (TextView) view.findViewById(R.id.album_photo_detail_sub_owner_posted_time_text_view);
mAlbumPhotoDetailPhotoPrice = (TextView) view.findViewById(R.id.album_photo_detail_photo_price);
mAlbumPhotoDetailSubDescription = (TextView) view.findViewById(R.id.album_photo_detail_sub_description);
mAddToCartButton = (Button) view.findViewById(R.id.album_photo_detail_photo_add_to_cart_button);
}
}
}
Here's my Interface
/**
* Interface..
*/
public interface ApiResponse {
public void apiResponseProcessing(String response);
}
Here's my Background AsyncTask
/**
* Background AsyncTask...
*/
public class BackgroundAsyncTask extends AsyncTask<Void, Void, String> {
private Context context;
private String accessToken;
private int memberId;
private int photoId;
private ApiResponse objIBaseApi;
public BackgroundAsyncTask(Context context, ApiResponse apiResponse, int memberId, int photoId) {
this.context = context;
this.memberId = memberId;
this.photoId = photoId;
accessToken = MemberPreference.getAccessToken(context);
this.objIBaseApi = apiResponse;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(Void... params) {
JSONObject json = JSONParser.addToCartPhoto(accessToken, memberId, photoId);
if(json != null) {
Log.i(TAG,"First Json : "+json.toString());
try {
if (json.getString(Fields.RESULT).equalsIgnoreCase(Fields.JSON_SUCCESS)) {
return Fields.JSON_SUCCESS;
} else if(json.getString(Fields.JSON_ERROR).equalsIgnoreCase(Fields.ERROR_ACCESS_DENIED)) {
String refreshToken = MemberPreference.getRefreshToken(context);
JSONObject newJSONObject = JSONParser.loginMemberWithRefreshToken(refreshToken, Integer.toString(memberId));
if(newJSONObject != null) {
if(newJSONObject.getString(Fields.JSON_ERROR).equalsIgnoreCase(Fields.ERROR_ACCESS_DENIED)) {
return Fields.ERROR_ACCESS_DENIED;
} else {
return Fields.JSON_SUCCESS;
}
} else
return Fields.ERROR_ACCESS_DENIED;
} else {
return Fields.JSON_ERROR;
}
} catch (JSONException e) {
e.printStackTrace();
return Fields.JSON_ERROR;
}
}
return Fields.JSON_ERROR;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
objIBaseApi.apiResponseProcessing(result);
}
}
Is there any solution or better way to do like this?
Your help would be appreciated. Thank you.
You Can keep one flag isAddedToCart variable in you bean class which you are using in your adapter(Photo). Now just pass the position in your asynctask once user click on "add to cart" button. On getting the successful you just need to find the bean from the list of bean you passed in adapter and change the flag isAddedToCart to true and notify your adapter thats it. Here is the code snippet:-
Photo Class
public class Photo{
private boolean isAddedToCart;
public void setAddedTOCart(boolean isAdded){
isAddedToCart = isAdded;
}
public boolean isAddedToCart(){
return isAddedToCart;
}
}
AlbumPhotoDetailAdapter onBindViewHolder
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
final Photo photo = mValues.get(position);
/**
* Album Owner Name
*/
String mOwnerName = photo.getOwnerName();
String mOwnerProfilePic = photo.getOwnerImage();
String mDateTime = photo.getDatetime();
String mPrice = String.valueOf(photo.getPrice());
String isAdded = photo.isAddedToCart();
/**
* Price String
*/
String priceStr = String.format(mContext.getString(R.string.string_dollar_price), mPrice);
holder.mAlbumPhotoDetailPhotoPrice.setText(priceStr);
/**
* Main Image
*/
Picasso.with(mContext).load(photo.getLink())
.error(R.drawable.ic_place_holder_circle)
.placeholder(R.drawable.ic_place_holder_circle)
.transform(new ImageTransformation(holder.mAlbumPhotoDetailSubMainImage))
.into(holder.mAlbumPhotoDetailSubMainImage);
/**
* Owner Name and Profile Pic
*/
holder.mAlbumPhotoDetailSubOwnerNameTextView.setText(mOwnerName);
Picasso.with(mContext).load(mOwnerProfilePic)
.error(R.drawable.ic_place_holder_circle)
.placeholder(R.drawable.ic_place_holder_circle)
.resize(100, 100)
.transform(new CircleTransform())
.into(holder.mAlbumPhotoDetailSubOwnerImage);
mDateTime = mDateTime != null ? DateUtils.getNiceTime(mDateTime) : "----";
holder.mAlbumPhotoDetailSubOwnerPostedTimeTextView.setText(mDateTime);
if(isAdded){
holder.mAddToCartButton.setText("Added TO Cart");
}else{
holder.mAddToCartButton.setText("Add TO Cart");
}
// Photo Add to cart.
holder.mAddToCartButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(InternetConnection.checkConnection(mContext)) {
new BackgroundAsyncTask(mContext, (ApiResponse) mContext, mMemberId, photo.getId(),position).execute();
} else {
DailyStudio.noInternetConnectionToast(mContext);
}
}
});
}
your Interface
public interface ApiResponse {
public void apiResponseProcessing(String response,int position);
}
Your Adapter apiResponceProcessing()
#Override
public void apiResponseProcessing(String response,int position) {
Log.i(TAG,"Api Response : "+response);
if(response.equals(Fields.JSON_SUCCESS)) {
mValues.get(position).setAddedTOCart(true);
notifyDataSetChange();
}
}
And finally your
BackgroundAsyncTask
public class BackgroundAsyncTask extends AsyncTask<Void, Void, String> {
private Context context;
private String accessToken;
private int memberId;
private int photoId;
private int mPosition;
private ApiResponse objIBaseApi;
public BackgroundAsyncTask(Context context, ApiResponse apiResponse, int memberId, int photoId,int position) {
this.context = context;
this.memberId = memberId;
this.photoId = photoId;
accessToken = MemberPreference.getAccessToken(context);
this.objIBaseApi = apiResponse;
this.mPosition = position;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(Void... params) {
JSONObject json = JSONParser.addToCartPhoto(accessToken, memberId, photoId);
if(json != null) {
Log.i(TAG,"First Json : "+json.toString());
try {
if (json.getString(Fields.RESULT).equalsIgnoreCase(Fields.JSON_SUCCESS)) {
return Fields.JSON_SUCCESS;
} else if(json.getString(Fields.JSON_ERROR).equalsIgnoreCase(Fields.ERROR_ACCESS_DENIED)) {
String refreshToken = MemberPreference.getRefreshToken(context);
JSONObject newJSONObject = JSONParser.loginMemberWithRefreshToken(refreshToken, Integer.toString(memberId));
if(newJSONObject != null) {
if(newJSONObject.getString(Fields.JSON_ERROR).equalsIgnoreCase(Fields.ERROR_ACCESS_DENIED)) {
return Fields.ERROR_ACCESS_DENIED;
} else {
return Fields.JSON_SUCCESS;
}
} else
return Fields.ERROR_ACCESS_DENIED;
} else {
return Fields.JSON_ERROR;
}
} catch (JSONException e) {
e.printStackTrace();
return Fields.JSON_ERROR;
}
}
return Fields.JSON_ERROR;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
objIBaseApi.apiResponseProcessing(result,mPosition);
}
}
Firstly in my opinion adapter should not care about network request. But
giving an answer in substance, you can try pass anonymous class for your apiResponseProcessing in same manner as you create OnClickListener for your button. It can look like this:
holder.mAddToCartButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(InternetConnection.checkConnection(mContext)) {
new BackgroundAsyncTask(
mContext,
new ApiResponse() {
#Override
public void apiResponseProcessing(String response) {
Log.i(TAG,"Api Response : "+response);
if(response.equals(Fields.JSON_SUCCESS)) {
// Here you can access you holder till it final
}
}
},
mMemberId,
photo.getId()).execute();
} else {
DailyStudio.noInternetConnectionToast(mContext);
}
}
});
But code like this looks messy and spaghetti. As i say at the beginning there are exist at least one different approach to handle changes for buttons inside listview/recivleview. I use method, where adapter only care about building interface with given data and delegate buttons clicks to someone else (in most cases activity that contains listview). An easy way notify activity about button click is Bus messaging pattern. I use Otto event library. When delegate receive notification about button click, it can initiate data changing according current task and then initiate listview reloading or partial update only required rows.
Additional comments
Try to write beautiful code. Constructor AlbumPhotoDetailAdapter has different syntax to assign instance variables. One with this keyword and other without. Usually you should use one way.
public AlbumPhotoDetailAdapter(Activity context, ArrayList<Photo> items) {
this.values = items;
this.context = context;
this.memberId = MemberPreference.getMemberId(context);
}
album instance variable have no access modifiers indication. You should know, that in java programming language omitting access specifiers is not the same as private modifier.
I want to use Volley to build my rest client. I found some good example (posted below) that do exactly what I want except the caching. I want to cache the results to the sdcard and load the cached result on starting the app. Any clue for how to modify the code below to enable caching for text and images of the list view items ?
/**
* Demonstrates: 1. ListView which is populated by HTTP paginated requests; 2. Usage of NetworkImageView;
* 3. "Endless" ListView pagination with read-ahead
*
* Please note that for production environment you will need to add functionality like handling rotation,
* showing/hiding (indeterminate) progress indicator while loading, indicating that there are no more records, etc...
*
* #author Ognyan Bankov (ognyan.bankov#bulpros.com)
*
*/
public class Act_NetworkListView extends Activity {
private static final int RESULTS_PAGE_SIZE = 20;
private ListView mLvPicasa;
private boolean mHasData = false;
private boolean mInError = false;
private ArrayList<PicasaEntry> mEntries = new ArrayList<PicasaEntry>();
private PicasaArrayAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act__network_list_view);
mLvPicasa = (ListView) findViewById(R.id.lv_picasa);
mAdapter = new PicasaArrayAdapter(this, 0, mEntries, new ImageLoader(queue, new BitmapCache(4)));
mLvPicasa.setAdapter(mAdapter);
mLvPicasa.setOnScrollListener(new EndlessScrollListener());
}
#Override
protected void onResume() {
super.onResume();
if (!mHasData && !mInError) {
loadPage();
}
}
RequestQueue queue;
private void loadPage() {
queue = MyVolley.getRequestQueue();
int startIndex = 1 + mEntries.size();
JsonObjectRequest myReq = new JsonObjectRequest(Method.GET,
"https://picasaweb.google.com/data/feed/api/all?q=kitten&max-results="
+
RESULTS_PAGE_SIZE
+
"&thumbsize=160&alt=json"
+ "&start-index="
+ startIndex,
null,
createMyReqSuccessListener(),
createMyReqErrorListener());
if (myReq.getCacheEntry().data != null) {
}
queue.add(myReq);
}
private Response.Listener<JSONObject> createMyReqSuccessListener() {
return new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
JSONObject feed = response.getJSONObject("feed");
JSONArray entries = feed.getJSONArray("entry");
JSONObject entry;
for (int i = 0; i < entries.length(); i++) {
entry = entries.getJSONObject(i);
String url = null;
JSONObject media = entry.getJSONObject("media$group");
if (media != null && media.has("media$thumbnail")) {
JSONArray thumbs = media.getJSONArray("media$thumbnail");
if (thumbs != null && thumbs.length() > 0) {
url = thumbs.getJSONObject(0).getString("url");
}
}
mEntries.add(new PicasaEntry(entry.getJSONObject("title").getString("$t"), url));
}
mAdapter.notifyDataSetChanged();
} catch (JSONException e) {
showErrorDialog();
}
}
};
}
private Response.ErrorListener createMyReqErrorListener() {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
showErrorDialog();
}
};
}
private void showErrorDialog() {
mInError = true;
AlertDialog.Builder b = new AlertDialog.Builder(Act_NetworkListView.this);
b.setMessage("Error occured");
b.show();
}
/**
* Detects when user is close to the end of the current page and starts loading the next page
* so the user will not have to wait (that much) for the next entries.
*
* #author Ognyan Bankov (ognyan.bankov#bulpros.com)
*/
public class EndlessScrollListener implements OnScrollListener {
// how many entries earlier to start loading next page
private int visibleThreshold = 5;
private int currentPage = 0;
private int previousTotal = 0;
private boolean loading = true;
public EndlessScrollListener() {
}
public EndlessScrollListener(int visibleThreshold) {
this.visibleThreshold = visibleThreshold;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
currentPage++;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// I load the next page of gigs using a background task,
// but you can call any function here.
loadPage();
loading = true;
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public int getCurrentPage() {
return currentPage;
}
}
The Adapter:
public class PicasaArrayAdapter extends ArrayAdapter<PicasaEntry> {
private ImageLoader mImageLoader;
public PicasaArrayAdapter(Context context,
int textViewResourceId,
List<PicasaEntry> objects,
ImageLoader imageLoader
) {
super(context, textViewResourceId, objects);
mImageLoader = imageLoader;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.lv_picasa_row, null);
}
ViewHolder holder = (ViewHolder) v.getTag(R.id.id_holder);
if (holder == null) {
holder = new ViewHolder(v);
v.setTag(R.id.id_holder, holder);
}
PicasaEntry entry = getItem(position);
if (entry.getThumbnailUrl() != null) {
holder.image.setImageUrl(entry.getThumbnailUrl(), mImageLoader);
} else {
holder.image.setImageResource(R.drawable.no_image);
}
holder.title.setText(entry.getTitle());
return v;
}
private class ViewHolder {
NetworkImageView image;
TextView title;
public ViewHolder(View v) {
image = (NetworkImageView) v.findViewById(R.id.iv_thumb);
title = (TextView) v.findViewById(R.id.tv_title);
v.setTag(this);
}
}
}
This is pretty easy with volley as caching is build in. All you need is to call getCache with url of the request. Of course keep in mind that server is responsible to set max age of the cache and if time was passed cache is cleared
For example try this:
if(queue.getCache().get(url)!=null){
//response exists
String cachedResponse = new String(queue.getCache().get(url).data);
}else{
//no response
queue.add(stringRequest);
}
For caching is great to use VolleyExtended: eu.the4thfloor.volleyextended There is updated Response listener, where you get additional boolean changed which means if response is different to last response in cache. And on error response there is additional parameter response (cached) which you can use in case of server or network outtage. Of course if it is cached only.
private class ResponseListenerYYY extends Api.ResponseListener {
#Override
public void onResponse(String response, boolean changed) {
}
#Override
public void onErrorResponse(VolleyError error, String response) {
}
}