How to modify View-pager on Real time basis - java

I, am new to android. and I created one view pager which fetching image from firebase. and It's working fine. but there are some loopholes in that view pager which I failed to solve.
1) My View-pager not getting update on real-time basis. Means when ever I modify image in firebase. it's does't effect on real-time basis. I have to close my app and then have to start it again for seeing those updates.
2) When I am removing item from my list in firebase. My app getting crashed and in Logcat this exception is thrown.
java.lang.IndexOutOfBoundsException: Index: -1, Size: 5
at java.util.ArrayList.add(ArrayList.java:483)
at com.release.pack.lootbox.Fragments.HomeFragment$7.onEvent(HomeFragment.java:294)
at com.release.pack.lootbox.Fragments.HomeFragment$7.onEvent(HomeFragment.java:281)
Here is my code:
Slider Image Adapter:
public class SliderImageAdapter extends SliderViewAdapter<SliderImageAdapter.SliderAdapterVH> {
public List<Banner> bannerList;
public Context context;
public SliderImageAdapter(Context context, List<Banner> bannerList) {
this.bannerList = bannerList;
this.context = context;
}
#Override
public SliderAdapterVH onCreateViewHolder(ViewGroup parent) {
View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_slider_myshop, parent, false);
return new SliderAdapterVH(inflate);
}
#Override
public void onBindViewHolder(final SliderAdapterVH viewHolder, final int position) {
Glide.with(viewHolder.itemView)
.load(bannerList.get(position).getmSliderImage())
.fitCenter()
.into(viewHolder.imageViewBackground);
}
#Override
public int getCount() {
return bannerList.size();
}
class SliderAdapterVH extends SliderViewAdapter.ViewHolder {
View itemView;
ImageView imageViewBackground;
ImageView imageGifContainer;
TextView textViewDescription;
public ProgressBar progressBar;
public ImageView imageViewFailed;
public SliderAdapterVH(View itemView) {
super(itemView);
imageViewBackground = itemView.findViewById(R.id.iv_auto_image_slider);
imageGifContainer = itemView.findViewById(R.id.iv_gif_container);
textViewDescription = itemView.findViewById(R.id.tv_auto_image_slider);
progressBar = itemView.findViewById(R.id.featured_deal_progress);
imageViewFailed = itemView.findViewById(R.id.featured_deal_img_failed_to_load);
this.itemView = itemView;
}
}
}
Home Fragment :
private SliderImageAdapter sliderImageAdapter;
private List<Banner> bannerList;
bannerList = new ArrayList<>();
sliderImageAdapter = new SliderImageAdapter(getActivity(),bannerList);
sliderMyshop = view.findViewById(R.id.imageSlider);
sliderMyshop.setSliderAdapter(sliderImageAdapter);
sliderMyshop.setIndicatorAnimation(IndicatorAnimations.WORM); //set indicator animation by using SliderLayout.IndicatorAnimations. :WORM or THIN_WORM or COLOR or DROP or FILL or NONE or SCALE or SCALE_DOWN or SLIDE and SWAP!!
sliderMyshop.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION);
sliderMyshop.setIndicatorSelectedColor(Color.WHITE);
sliderMyshop.setIndicatorUnselectedColor(Color.GRAY);
sliderMyshop.startAutoCycle();
mFirestore.collection("Banner").orderBy("priority", Query.Direction.DESCENDING).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#javax.annotation.Nullable QuerySnapshot documentSnapshots, #javax.annotation.Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.d(TAG, "Error : " + e.getMessage());
}
assert documentSnapshots != null;
for (DocumentChange doc : documentSnapshots.getDocumentChanges()) {
if (doc.getType() == DocumentChange.Type.ADDED) {
String doc_id = doc.getDocument().getId();
Banner banner = doc.getDocument().toObject(Banner.class).withDocId(doc_id);
bannerList.add(doc.getNewIndex(), banner);
sliderImageAdapter.notifyDataSetChanged();
} else if (doc.getType() == DocumentChange.Type.MODIFIED) {
String docID = doc.getDocument().getId();
Banner changedModel = doc.getDocument().toObject(Banner.class).withDocId(docID);
if (doc.getOldIndex() == doc.getNewIndex()) {
// Item changed but remained in same position
bannerList.set(doc.getOldIndex(), changedModel);
// sliderImageAdapter.notifyItemChanged(doc.getOldIndex());
} else {
// Item changed and changed position
bannerList.remove(doc.getOldIndex());
bannerList.add(doc.getNewIndex(), changedModel);
// sliderImageAdapter.notifyItemMoved(doc.getOldIndex(), doc.getNewIndex());
}
} else if (doc.getType() == DocumentChange.Type.REMOVED) {
// remove
bannerList.remove(doc.getOldIndex());
// sliderImageAdapter.notifyItemRemoved(doc.getOldIndex());
}
}
}
});
When I am adding a new image in firebase it's getting update on real-time basis. but not when I am doing modification in my image. or removing the item from my list.

Getting crashed
bannerList.add(doc.getNewIndex(), banner);
This line is the reason for the IndexOutOfBoundsException.
(java.lang.IndexOutOfBoundsException: Index: -1, Size: 5)
doc.getNewIndex() is returning -1. and list can not accept -1 as an index. so please check your doc.getNewIndex().
For realtime update
bannerList.clear();
add this code before your for loop, otherwise redundant data will be added and you can not update the new changes.
See this: The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified).
for every changes whole collection will be fetched

Related

Android Stop recycler-View adapter binding data for already shown items

I have an app that uses a recyclerView to show results from Google Books API.
In every onBindViewHolder, I ask the client to give me data which leads me eventually to exceeding the rate limit since every scroll calls data.
Let say I got data for position 1-5 and I scroll down to position 6-10 and then go back to 1-5. How can I make sure that it won't call the client again for positions 1-5 since it has already loaded them?
I just want whatever it already called to stay there.
My adapter looks like this ( I deleted some parts like more views so it wont confuse):
public class MyBooksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<DiscoverBooks> discoverBooks;
private FirebaseFirestore db;
private FirebaseAuth auth;
private String UID, BookID2;
private int count, gbsSize, counter;
interface OnDiscoverBookClickListener {
void onClick(DiscoverBooks discoverBooks);
}
private OnDiscoverBookClickListener listener;
public MyBooksAdapter(int counter, List<DiscoverBooks> discoverBooks, int gbsSize, OnDiscoverBookClickListener listener) {
this.counter = counter;
this.discoverBooks = discoverBooks;
this.listener = listener;
this.gbsSize = gbsSize;
}
#Override
public int getItemViewType(int position) {
return AppConstants.IS_NOT_ADS_POSITION;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view;
view = LayoutInflater.from( viewGroup.getContext() ).inflate( R.layout.item_book_mybook, viewGroup, false );
return new DiscoverBooksViewHolder( view );
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.setIsRecyclable( false );
if (holder instanceof DiscoverBooksViewHolder) {
((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind( (discoverBooks.get( position )), holder );
}
}
#Override
public int getItemCount() {
return discoverBooks.size();
}
class DiscoverBooksViewHolder extends RecyclerView.ViewHolder {
private Button tv_Link;
private CardView cardView;
private ImageView Iv_BookCover;
private TextView tv_Author, tv_BorrowedTo, tv_BorrowedTill, tv_DateAdded, tv_Title;
private ImageButton ib_Options;
private ToggleButton tb_Status;
private DiscoverBooks discoverBooks;
private DiscoverBooksViewHolder(View itemView) {
super( itemView );
cardView = itemView.findViewById( R.id.imagecard );
Iv_BookCover = itemView.findViewById( R.id.iv_BookCover );
tv_Title = itemView.findViewById( R.id.tv_Title );
tv_Author = itemView.findViewById( R.id.tv_Author );
tv_DateAdded = itemView.findViewById( R.id.tv_DateAdded );
tv_BorrowedTo = itemView.findViewById( R.id.tv_BorrowedTo );
tv_BorrowedTill = itemView.findViewById( R.id.tv_BorrowedTill );
tv_Link = itemView.findViewById( R.id.tv_Link );
ib_Options = itemView.findViewById( R.id.ib_Options );
tb_Status = itemView.findViewById( R.id.tb_Status );
}
private void bind(DiscoverBooks discoverBooks, RecyclerView.ViewHolder viewHolder) {
this.discoverBooks = discoverBooks;
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
String BookID = discoverBooks.getBookID();
if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
db.collection( "Books" ).document( BookID ).get()
.addOnCompleteListener( task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
DO SOMETHING
}
}
} );
} else {
//HERE I CALL GOOGLE BOOKS API
MyBookClient.getInstance().getBooks( BookID, new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
if (response != null) {
final MyBook books = MyBook.fromJson( response );
//DO SOMETHING
}
}
} );
}
}
}
}
Ok, I understood your problem. For every bookID, you fetch its data from Google Books, and now you want to not hit the api for already loaded items.
Simply, create a global variable for ArrayList as
private ArrayList<MyBook> mybooks = new ArrayList();
Call your onBind() in onBindViewHolder() as
((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind(position, discoverBooks.get( position), holder);
Replace your onBind() with this:
private void bind(int position, UserData discoverBooks, RecyclerView.ViewHolder viewHolder) {
this.discoverBooks = discoverBooks;
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
String BookID = discoverBooks.getBookID();
if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
db.collection("Books").document(BookID).get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
DO SOMETHING
}
}
});
} else {
if (position >= myBooks.size() || myBooks.get(position) == null) {
//If the value doesn't exist in the ArrayList,
//it will hit the Google Books API
MyBookClient.getInstance().getBooks(BookID, new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
if (response != null) {
final MyBook books = MyBook.fromJson(response);
myBooks.add(position, books);
//DO SOMETHING
}
}
});
} else {
//If the value has already been loaded in the list,
//directly use it without hitting the API
final MyBook books = myBooks.get(position);
//DO SOMETHING
}
}
}
This will solve your issue, it will store the already accessed books from the API and will not hit the API again and will use the List to access them.
Apart from this, I'll suggest to go with ViewBinding which will replace your whole DiscoverBooksViewHolder into a single line(in Kotlin) or 2-3 lines(in Java), with viewBinding, you'll not have to declare and initialize views and the views can be accessed directly using Binding, you can then extract your onBind() out of the ViewHolder.

Why is the drag animation being repeated in RecyclerView?

I am using the ItemTouchHelper class to support drag and drop in my RecyclerView. While I am dragging an item around it visually updates (swaps rows) as expected. Once I drop the item, another **visual** drag occurs. For example (see diagram below) if I drag item "a" from index 0 to index 3, the correct list shows that item "b" is now at index 0. They recycler view repeats the operation and takes the new item at index 0 ("b") and drags it to index 3! This repeated drag happens no matter what index I drag from or to.
I called it a **visual** drag because the list I am submitting to my RecyclerView's ListAdapter is correctly ordered (verified by logs). And if I restart my app the list is in the correct order. Or if I call notifyDataSetChanged(), after the unwanted animation, it will order itself properly. What could be causing this second animation?
EDIT: According to the documentation, if you use equals() method in your areContentsTheSame() method (DiffUtil), "Incorrectly returning false here will result in too many animations." As far as I can tell, I am properly overriding this method in my POJO file below. I am stumped...
MainActivity.java
private void setListObserver() {
viewModel.getAllItems().observe(this, new Observer<List<ListItem>>() {
#Override
// I have verified newList has the correct order through log statements
public void onChanged(List<ListItem> newList) {
adapterMain.submitList(newList);
}
});
}
...
// This method is called when item starts dragging
public void onSelectedChanged(#Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
...
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
currentList = new ArrayList<>(adapterMain.getCurrentList()); // get current list from adapter
}
...
}
// This method is called when item is dropped
public void clearView(#NonNull RecyclerView recyclerView,
#NonNull RecyclerView.ViewHolder viewHolder) {
...
// I have verified that all code in this method is returning correct values through log statements.
// If I restart the app, everything is in the correct order
// this is position of the where the item was dragged to, gets its value from the onMoved method.
// it's the last "toPos" value in onMoved() after the item is dropped
int position = toPosition;
// Skip this code if item was deleted (indicated by -1). Otherwise, update the moved item
if(position != -1) {
ListItem movedItem = currentList.get(position);
// If dragged to the beginning of the list, subtract 1 from the previously lowest
// positionInList value (the item below it) and assign it the moved item. This will ensure
// that it now has the lowest positionInList value and will be ordered first.
if(position == 0) {
itemAfterPos = currentList.get(position + 1).getPositionInList();
movedItemNewPos = itemAfterPos - 1;
// If dragged to the end of list, add 1 to the positionInList value of the previously
// largest value and assign to the moved item so it will be ordered last.
} else if (position == (currentList.size() - 1)) {
itemBeforePos = currentList.get(position - 1).getPositionInList();
movedItemNewPos = itemBeforePos + 1;
// If dragged somewhere in the middle of list, get the positionInList variable value of
// the items before and after it. They are used to compute the moved item's new
// positionInList value.
} else {
itemBeforePos = currentList.get(position - 1).getPositionInList();
itemAfterPos = currentList.get(position + 1).getPositionInList();
// Calculates the moved item's positionInList variable to be half way between the
// item above it and item below it
movedItemNewPos = itemBeforePos + ((itemAfterPos - itemBeforePos) / 2.0);
}
updateItemPosInDb(movedItem, movedItemNewPos);
}
private void updateItemPosInDb(ListItem movedItem, double movedItemNewPos) {
movedItem.setPositionInList(movedItemNewPos);
viewModel.update(movedItem); // this updates the database which triggers the onChanged method
}
public void onMoved(#NonNull RecyclerView recyclerView,
#NonNull RecyclerView.ViewHolder source, int fromPos,
#NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
Collections.swap(currentList, toPos, fromPos);
toPosition = toPos; // used in clearView()
adapterMain.notifyItemMoved(fromPos, toPos);
}
}).attachToRecyclerView(recyclerMain);
RecyclerAdapterMain.java
public class RecyclerAdapterMain extends ListAdapter<ListItem, RecyclerAdapterMain.ListItemHolder> {
// Registers MainActivity as a listener to checkbox clicks. Main will update database accordingly.
private CheckBoxListener checkBoxListener;
public interface CheckBoxListener {
void onCheckBoxClicked(ListItem item); // Method implemented in MainActivity
}
public void setCheckBoxListener(CheckBoxListener checkBoxListener) {
this.checkBoxListener = checkBoxListener;
}
public RecyclerAdapterMain() {
super(DIFF_CALLBACK);
}
// Static keyword makes DIFF_CALLBACK variable available to the constructor when it is called
// DiffUtil will compare two objects to determine if updates are needed
private static final DiffUtil.ItemCallback<ListItem> DIFF_CALLBACK =
new DiffUtil.ItemCallback<ListItem>() {
#Override
public boolean areItemsTheSame(#NonNull ListItem oldItem, #NonNull ListItem newItem) {
return oldItem.getId() == newItem.getId();
}
// Documentation - NOTE: if you use equals, your object must properly override Object#equals().
// Incorrectly returning false here will result in too many animations.
// As far as I can tell I am overriding the equals() properly in my POJO below
#Override
public boolean areContentsTheSame(#NonNull ListItem oldItem, #NonNull ListItem newItem) {
return oldItem.equals(newItem);
}
};
#NonNull
#Override
public ListItemHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item_layout_main, parent, false);
return new ListItemHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull ListItemHolder holder, int position) {
ListItem item = getItem(position);
Resources resources = holder.itemView.getContext().getResources();
holder.txtItemName.setText(item.getItemName());
holder.checkBox.setChecked(item.getIsChecked());
// Set the item to "greyed out" if checkbox is checked, normal color otherwise
if(item.getIsChecked()) {
holder.txtItemName.setTextColor(Color.LTGRAY);
holder.checkBox.setButtonTintList(ColorStateList
.valueOf(resources.getColor(R.color.checkedColor, null)));
} else {
holder.txtItemName.setTextColor(Color.BLACK);
holder.checkBox.setButtonTintList(ColorStateList
.valueOf(resources.getColor(R.color.uncheckedColor, null)));
}
}
public class ListItemHolder extends RecyclerView.ViewHolder {
private TextView txtItemName;
private CheckBox checkBox;
public ListItemHolder(#NonNull View itemView) {
super(itemView);
txtItemName = itemView.findViewById(R.id.txt_item_name);
// Toggle checkbox state
checkBox = itemView.findViewById(R.id.checkBox);
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkBoxListener.onCheckBoxClicked(getItem(getAdapterPosition()));
}
});
}
}
public ListItem getItemAt(int position) {
return getItem(position);
}
}
ListItem.java (POJO)
#Entity(tableName = "list_item_table")
public class ListItem {
#PrimaryKey(autoGenerate = true)
private long id;
private String itemName;
private boolean isChecked;
private double positionInList;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
public boolean getIsChecked() {
return isChecked;
}
public void setPositionInList(double positionInList) {
this.positionInList = positionInList;
}
public double getPositionInList() {
return positionInList;
}
#Override
public boolean equals(#Nullable Object obj) {
ListItem item = new ListItem();
if(obj instanceof ListItem) {
item = (ListItem) obj;
}
return this.getItemName().equals(item.getItemName()) &&
this.getIsChecked() == item.getIsChecked() &&
this.getPositionInList() == item.getPositionInList();
}
}

Improve Large ListView Adapter smooth scroll, sometimes jerky

I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely).
I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
public class VListAdapter extends BaseAdapter {
ViewHolder viewHolder;
private boolean isItFromProfile;
/**
* fields For number formating, ex. 1000
* would return 1k in the format method
*/
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<Post> mDataSource;
private static double lat;
private static double lon;
public VListAdapter(Context context, ArrayList<Post> items) {
mContext = context;
mDataSource = items;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = false;
mInflater = LayoutInflater.from(context);
}
public VListAdapter() {
}
public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
mContext = baseContext;
mDataSource = posts;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = b;
mInflater = LayoutInflater.from(baseContext);
}
public void addElement(Post post) {
mDataSource.add(0, post);
this.notifyDataSetChanged();
}
#Override
public int getCount() {
return mDataSource.size();
}
#Override
public Object getItem(int position) {
return mDataSource.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
int limit = Math.min(position + 4, getCount());
for (int i = position; i < limit; i++) {
Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
}
// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
// .detectDiskWrites().detectNetwork()
// .penaltyLog().build());
View rowView = convertView;
if (rowView == null) {
viewHolder = new ViewHolder();
rowView = mInflater.inflate(R.layout.mylist, parent, false);
viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
//viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
final Post post = (Post) getItem(position);
int color = Color.parseColor("#dddddd");
viewHolder.likesPic.setColorFilter(color);
viewHolder.mapitPic.setColorFilter(color);
viewHolder.moremenu.setColorFilter(color);
if (Hawk.count() == 0)
initHawkWithDataFromServer();
if (isItFromProfile) {
viewHolder.profilePic.setVisibility(View.GONE);
viewHolder.titleTextView.setVisibility(View.GONE);
viewHolder.distance.setVisibility(View.GONE);
}
viewHolder.titleTextView.setText(post.getUsername());
PrettyTime prettyTime = new PrettyTime();
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
if (post.getViews() != null)
viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
+ ".jpg";
String filename = post.getS3link();
final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;
Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
}
});
int height = ((Post) getItem(position)).getHeight();
int width = ((Post) getItem(position)).getWidth();
if (height != 0 && width != 0) {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
}
if (post.getType() == null) {
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
//Glide.clear(viewHolder.listphoto);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
viewHolder.playbutton.setVisibility(View.VISIBLE);
}
if (post.getType() != null) {
if (post.getType().equals("video")) {
viewHolder.playbutton.setVisibility(View.VISIBLE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
}
if (post.getType().equals("image")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.listphoto.setVisibility(View.VISIBLE);
viewHolder.listphoto.setBottom(0);
Glide.with(mContext).load(post.getFilename().toString())
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
.into(viewHolder.listphoto);
}
if (post.getType().equals("text")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
viewHolder.postText.setVisibility(View.VISIBLE);
viewHolder.postText.setText(post.getText());
}
}
if (Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.clearColorFilter();
Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
} else {
Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.likesPic);
}
if (Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.clearColorFilter();
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
} else {
Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.mapitPic);
}
if (!Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Hawk.put("mapped" + post.getId().get$oid(), 1);
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postMappedToServer(post.getId().get$oid());
}
});
t.start();
TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
}
});
} else {
viewHolder.mapitPic.setClickable(false);
}
if (!Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Hawk.put("liked" + post.getId().get$oid(), 1);
viewHolder.likesPic.setClickable(false);
((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
post.setLikes(post.getLikes() + 1);
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postLikeToServer(post);
}
});
t.start();
}
});
} else {
viewHolder.likesPic.setClickable(false);
}
if (post.getType() == null || post.getType().equals("video"))
viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (VListAdapter.this.mContext instanceof ProfileFeed) {
((ProfileFeed) VListAdapter.this.mContext).closeActivity();
}
Intent broadcast = new Intent();
broadcast.setAction("com.molehead.openout.POST");
broadcast.putExtra("postId", post.getFilename().toString());
broadcast.putExtra("hawkId", post.getId().get$oid());
broadcast.putExtra("s3link", post.getS3link());
broadcast.putExtra("username", post.getUsername());
if (Hawk.contains("liked" + post.getId().get$oid()))
broadcast.putExtra("liked", "yes");
else
broadcast.putExtra("liked", "no");
broadcast.putExtra("likecount", post.getLikes().toString());
App.post = post;
LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
}
});
viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
//Inflating the Popup using xml file
popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
//registering popup with OnMenuItemClickListener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
String postId = post.getId().get$oid();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
String shareSub = "Shared via Molehead";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent new_intent = Intent.createChooser(sharingIntent, "Share");
new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(new_intent);
break;
}
return true;
}
});
popup.show();
}
});
return rowView;
}
private void initHawkWithDataFromServer() {
SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
final Call<List<Post>> call = loginService.getLikes(username);
Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat));
call.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
ArrayList<Post> posts = new ArrayList<>();
posts = (ArrayList<Post>) response.body();
if (!posts.isEmpty())
for (Post p : posts) {
Hawk.put("liked" + p.getId().get$oid(), 1);
}
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
}
});
}
private void postMappedToServer(String oid) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful())
Log.i("mapped", "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
public void postLikeToServer(Post post) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Call<ResponseBody> call = loginService.like(post, 1, username);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
Log.i("call", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("MFEED", "like request failed");
}
});
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
static class ViewHolder {
private TextView titleTextView;
private TextView timeago;
private TextView likesTextView;
private TextView viewcount;
private TextView distance;
private TextView footprints;
private ImageView profilePic;
private ImageView moremenu;
private ImageView likesPic;
private ImageView mapitPic;
private ImageView rainbow;
//private ImageView sharebutton;
private TextView caption;
private ImageView listphoto;
private ImageView videoThumb;
private ImageView playbutton;
private TextView postText;
private Post post;
}
private float getHeight(float height, float width) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return (height * size.x / width);
}
}
It is impossible to point to a specific issue because there is so much code in your Adapter. One thing is sure, though - switching to RecyclerView won't help you in this case.
Adapters should not contain business logic - they should only "adapt" input objects to the underlying Views. In your case, it seems like the adapter performs calculations, spawns new threads, performs network requests, etc.
You need to refactor your code such that the adapter will be similar to this:
public class PostsListAdapter extends ArrayAdapter<Post> {
private Context mContext;
public PostsListAdapter(Context context, int resource) {
super(context, resource);
mContext = context;
}
public void bindPosts(List<Post> posts) {
clear();
addAll(posts);
notifyDataSetChanged();
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// assign new View to convertView
// create new ViewHolder
// set ViewHolder as tag of convertView
// set listeners
} else {
// get a reference to existing ViewHolder
}
// populate ViewHolder's elements with data from getItem(position)
// kick off asynchronous loading of images
// NOTE: no calculations allowed here - just simple bidding of data to Views
return convertView;
}
}
Your code needs to be structured in such a way, that business logic that involves calculations and transformation of data executed before you bind a new data to ListView, and Post objects that you pass to bindPosts() method already contain the results of the aforementioned calculations and transformations.
Adapter just "adapts" the final data from Posts to Views - nothing more.
If you're short on time now, and just need to "make it work", then I would start by removing the logic that spawns new threads and makes network requests. See if this improves performance.
Change your implementation to RecyclerView which is more efficient in terms of scrapping views or recycling.
We can also enable optimizations if the items are static and will not change for significantly smoother scrolling:
recyclerView.setHasFixedSize(true);
Create an intent service and register BroadcastReceiver as data return callback or error callback when api request, business rule, data modification completed. Use synchronous call to execute initHawkWithDataFromServer() in advance and after getting result from api continue modifying or applying business logic. After that create new adapter or update existing adapter data set.
Move all the below data calculation or data value formatting logic from adapter's getView() to above intent service.
You can add more getter and setter to existing Post pojo.
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
Post{
//Your existing property
#Expose(serialize = false, deserialize = false)
//equals neither serialize nor deserialize or
private DateTime uploadedDateTime;
//etc. prettyTime.format, String.valueOf
}
Removes unnecessary reflection:
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
Gson gson = builder.create();
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
and it to your retrofit Service creation class.
You can also use transient(private transient DateTime uploadedDateTime;)
Remove
public void addElement(Post post) { mDataSource.add(0, post);
this.notifyDataSetChanged();}
and whenever you need to notify if a single or more items inserted, deleted etc. Use the below:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
We can use these from the activity or fragment:
//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);
Above methods are more efficient. Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());
Diffing Larger Changes
A new DiffUtil class has been added in the v24.2.0 of the support library to help compute the difference between the old and new list. Details
Don't preload images via glide if your image sizes are different. Try creating your own. Also try looking at.
Create color as the class member
int color = Color.parseColor("#dddddd");
Write View.GONE or View.VISIBLE in Post pojo itself, which will be executed in background thread from Retrofit if IntentService. Try api return boolean in json instead "0" as String.
Move all below to IntentService
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
All String concatenation also in Post or IntnetService like:
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
Also you can create color filter in advance and one time only. Remove scrollbar from listview as it calculates height to show scroll bar.
Too many things to improve here. Here is some examples.
I see this
if (Hawk.count() == 0)
initHawkWithDataFromServer();
I believe that the method initHawkWithDataFromServer will be called many times during the time the list appears.
This call can be done only once when the activity was created.
Glide.with(mContext).load(videoThumbURL).fitCenter()
But you should refactor your code first, moving the logic to another class. Try to remove some code like this (it should be done by using some layout attribites)
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);

Proper way to update ListView with data?

I am using ParseQueryAdapter like so:
public class MyPostedSalesAdapter extends ParseQueryAdapter<Sale> {
static String mTAG = "LocalSalesAdapter";
static ParseUser mCurrentUser = ParseUser.getCurrentUser();
public MyPostedSalesAdapter(Context context) {
super(context, new ParseQueryAdapter.QueryFactory<Sale>() {
#SuppressWarnings("unchecked")
public ParseQuery<Sale> create() {
#SuppressWarnings("rawtypes")
ParseQuery query = new ParseQuery("Sales");
query.orderByDescending("createdAt");
query.whereEqualTo("postedBy", mCurrentUser.getUsername());
try {
int salesAmount = query.count();
ProfileActivity.setTextView(salesAmount);
} catch (ParseException e) {
e.printStackTrace();
}
return query;
}
});
}
#Override
public View getItemView(Sale sale, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.listview_cell, null);
}
super.getItemView(sale, v, parent);
ParseImageView saleImage = (ParseImageView) v.findViewById(R.id.icon);
ParseFile photoFile = sale.getParseFile("photo");
if (photoFile != null) {
saleImage.setParseFile(photoFile);
saleImage.loadInBackground(new GetDataCallback() {
#Override
public void done(byte[] data, ParseException e) {
}
});
} else {
// Clear ParseImageView if the object has no photo, set placeholder.
saleImage.setImageResource(R.drawable.placeholder);
}
TextView titleTextView = (TextView) v.findViewById(R.id.textView_listView_saleTitle);
titleTextView.setText(sale.getSaleTitle());
TextView priceTextView = (TextView) v.findViewById(R.id.textView_listView_salePrice);
priceTextView.setText(sale.getSalePrice());
return v;
}
}
And simply onResume, setting the adapter to ListView like so:
#Override
protected void onResume() {
super.onResume();
mCurrentUser = ParseUser.getCurrentUser();
// Setting up currentUser to current logged in user
// If user is not logged in, present them with Login Activity
if (mCurrentUser == null || !isUserOnline())
{
presentUserWithLogin();
}
else
{
mOwnSalesAdapter = new MyPostedSalesAdapter(this);
mOwnSalesAdapter.loadObjects();
mUserSales.setAdapter(mOwnSalesAdapter);
}
}
I did this onResume, thinking every time the Activity is loaded, new data is pulled using query and displayed in the ListView. However this is not the case, and occasionally will act like this. Normally I have to close the entire app for the ListView to update properly and display any updates to the data. Even If I were to logout of the current parse user, and login on another account without closing the app, the previous user's posts sometimes appear as well in the listview. What am I doing wrong? I never encountered such problems with working in iOS. Also noticed if I were to delete an Item from Parse and go back, usually the item still appears in the ListView.
Hi just call in onResume()
if(mOwnSalesAdapter!=null)
mOwnSalesAdapter.notifyDataSetChanged();

BaseAdapter notifyDatasetChanged() called but getView() is never called

I have a custom adapter that visualize each row in the list of Orders.
public class OrderRowAdapter extends BaseAdapter implements OnClickListener {
OrderList items_;
LayoutInflater inflater_;
int list_view_resource_id_;
private final String TAG = "OrderRowAdapter";
public OrderRowAdapter(Context context, int list_view_resource_id,
OrderList items) {
this.list_view_resource_id_ = list_view_resource_id;
this.items_ = items;
this.inflater_ = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public Object getItem(int position) {
return items_.getOrders(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "View updated for item in position = " + position);
View v = convertView;
if (v == null) {
v = inflater_.inflate(list_view_resource_id_, parent);
}
Order item = items_.getOrders(position);
if (item != null) {
TextView order_info_tv = (TextView) v.findViewById(R.id.order_info);
TextView order_status_tv = (TextView) v.findViewById(R.id.order_status);
if (order_info_tv != null) {
order_info_tv.setText(
String.format("For customer: %s\nTotal of %d items", item.getCustomerId(), item.getItemsCount()));
}
if (order_status_tv != null) {
order_status_tv.setText("Status: " + getStatusText(item.getStatus()));
}
}
return v;
}
public int getCount() {
if (items_ == null) {
Log.d(TAG, "Null so get count returned 0");
return 0;
} else {
Log.d(TAG, "Get count returned " + items_.getOrdersCount());
return items_.getOrdersCount();
}
};
After querying new list of orders from a web service, I want to update the content of the ListView, so I have my Activity does the updating before calling notifyDataSetChanged()
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.orders);
initThreading();
findViews();
setUrls();
// Load the list of order from disk
try {
order_list_ = OrderList.parseFrom(new FileInputStream(
"/sdcard/orderList.bin"));
} catch (FileNotFoundException e) {
Log.e(TAG, "Cannot find the file", e);
} catch (IOException e) {
Log.e(TAG, "Cannot read the file", e);
}
order_row_adapter_ = new OrderRowAdapter(OrderActivity.this,
R.layout.order_row, order_list_);
orders_listview_.setAdapter(order_row_adapter_);
// Request new updates from the server
updateOrderInformation(-1);
}
public void updateOrders(InputStream new_order_stream) {
Log.d(TAG, "Updating order UI");
try {
order_list_.parseFrom(new_order_stream);
} catch (IOException e) {
Log.e(TAG, "IOException" , e);
}
runOnUiThread(new Runnable() {
public void run() {
guiUpdateOrders();
}
});
}
private void guiUpdateOrders() {
order_row_adapter_.notifyDataSetChanged();
Log.d(TAG, "Dataset notified that it has changed. GUI update anytime now.");
}
But, the getView() method of OrderRowAdapter is never called. The ListView is never updated.
It turns out the problem with my getView() not being called is because it is not visible. My layout xml has the upper TextView with fill_parent for its height. Thus the entire view only has that single TextView visible.
Solution: check the graphical view of the layout in question to make sure the ListView is visible.
Make sure BaseAdapter methods
registerDataSetObserver(DataSetObserver observer)
unregisterDataSetObserver(DataSetObserver observer)
are not overridden.
To change the content of your ListView, you must keep using the same reference to the List. Here you're creating another list and assigning it to the items_ variable (which does not contain the list itself, it's just a place to store a reference to a List), but your View still has a reference to the old list.
Instead of items_ = new_order_list this should work :
items_.clear();
items_.addAll(new_order_list);
EDIT :
To explain it better, try to create a new variable named old_items :
public void setNewOrderList(List<Order> new_order_list)
{
Log.d(TAG, "New Order List available. Num items = " + new_order_list.size());
List<Order> old_items = items_; // store the reference to the old list
items_ = new_order_list;
Log.d(TAG, "items_.size() = " + items_.size());
Log.d(TAG, "old_items.size() = " + old_items.size()); // The old list still exists, and it's the one used by your ListView
notifyDataSetChanged();
}
If all above answers not working try with invalidateViews()
ListView.invalidateViews() is used to tell the ListView to invalidate all its child item views (redraw them).
Note that there not need to be an equal number of views than items. That's because a ListView recycles its item views and moves them around the screen in a smart way while you scroll.
listView.invalidateViews()
My sample implementation,
lvitems.post(new Runnable() {
#Override
public void run() {
lvitems.invalidateViews(); //invalidate old
CustomAdapter customAdapter=new CustomAdapter(context, names, images); // load new data
customAdapter.notifyDataSetChanged();// call notifydatasetChanged
}
});
If any mistake in this answer please correct my mistakes.

Categories

Resources