I have a problem using FirebaseRecyclerAdapater, at first it was working fine but now this adapter is firing twice. The database reference is only referring one child, but it is always firing twice. The Toast with text "counter" will appear twice
FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder> requestAdapter =
new FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder>(
RequestVisit.class,
R.layout.seekerrequests_layout,
RequestViewHolder.class,
requestDatabase.child("2DBwmhGplGMoAlLy6337HZEShi93")
) {
#Override
protected void populateViewHolder(final RequestViewHolder viewHolder, RequestVisit model, int position) {
Toast.makeText(getContext(), "counter" +
viewHolder.getAdapterPosition(), Toast.LENGTH_SHORT).show();
}
};
requestVisitList.setAdapter(requestAdapter);
A Firebase*Adapter shows a list of items, the child nodes under the location that you attach it to.
If populateViewHolder gets called with two different positions, that means there are two children under requestDatabase.child("2DBwmhGplGMoAlLy6337HZEShi93").
Keep in mind that if 2DBwmhGplGMoAlLy6337HZEShi93 is a child node with two properties, then your approach will call populateViewHolder for each of those properties.
If you want to show only a single item in the RecyclerView, you can create a simple query with:
FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder> requestAdapter =
new FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder>(
RequestVisit.class,
R.layout.seekerrequests_layout,
RequestViewHolder.class,
requestDatabase.orderByKey().equalTo("2DBwmhGplGMoAlLy6337HZEShi93")
)
Related
I'm getting the list of user ids from database. And its getting the values correctly. I'm using FirebaseRecyclerAdapter and when I iterate that list in onBindViewHolder the loops runs as the size of list. For example over here :
adapter = new FirebaseRecyclerAdapter<Profiles, DonarViewHolder>(model) {
#Override
protected void onBindViewHolder(#NonNull DonarViewHolder holder, int position, #NonNull Profiles profiles) {
for (int i = 0 ; i<list.size() ; i++){
Toast.makeText(List.this,"List :" +list.get(i), Toast.LENGTH_SHORT).show();
}
}
}
Over here I have 5 item in list and while iterating I'm toasting each item . So in here the loop run for 25 time, its shows each item 5 time in sequence. First it shows 5 items orderly after showing last item it start toasting from first one again and continues till 5 time as the size of list.
So how do I cater this and make it only iterate the whole items for once only?
onBindViewHolder is called by RecyvlerView to display the data at the specified position. Since you have all the data in a List, their corresponding index will be the same as the position of the itemView.
In your function, you are iterating over all the items in the list for each itemView which the ViewHolder is generating.
You have to remove the for loop from your code and simply use the position as the index to retrieve data from your list, whose index is same as position.
Try this code:
adapter = new FirebaseRecyclerAdapter<Profiles, DonarViewHolder>(model) {
#Override
protected void onBindViewHolder(#NonNull DonarViewHolder holder, int position, #NonNull Profiles profiles) {
Toast.makeText(List.this,"List :" +list.get(position), Toast.LENGTH_SHORT).show();
}
}
For this method, every time an itemView is generated, it will be at a position and with this as an index, you can easily get the data from the list.
For your scenario, since you are already inside each itemView being generated inside the onBindViewHolder, and then you are iterating for each element in your list, hence every item is being displayed exactly the same number of times, as the size of the list.
I think you are missing a method that you can use to call each item individually.
create a setter and getter for your item. In your loop you can then call it list.get(position).getItem .
Edit: if anyone is having the same problem in the future it was a pretty easy fix. I used clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) to handle when the entire drag was finished and had forgotten to call it's super-method. That's why it didn't update properly.
Original question:
Here's the entire code: https://github.com/vustav/Ppaaiinntt/tree/master/app/src/main/java/com/decent/rvtest
Everything works fine except when I add an element right after deleting another. It does exist though. If I add another there's a properly sized space between the old list and the new element. I use a string where they add their names before a print and it shows there, and if I drag to change positions of the elements it shows up properly.
My reputation doesn't allow me to post images so an imgur-album will have to do:
http://imgur.com/a/bmb17
On the first image there's three elements and the string is printed at the bottom.
The second image is after the swipe. Notice the string is updated.
The third is after adding another "111". The string is correct but it doesn't show up in the view.
The fourth is after adding another. The string is still correct and the new element shows up in the view.
The last image is after dragging to change the position of the last two elements. Now everything is fine again.
These are the relevant methods (I think):
protected void add(PictureElement pe){
chain.add(pe);
notifyItemInserted(chain.size()-1);
}
public void remove(int position) {
chain.remove(position);
notifyItemRemoved(position);
}
protected void swap(int from, int to){
chain.swap(from, to);
notifyItemMoved(from, to);
}
Edit: onBindViewHolder, getItemCount and the ViewHolder:
#Override
public int getItemCount() {
return chain.size();
}
#Override
public void onBindViewHolder(PEViewHolder PEViewHolder, int i) {
PictureElement pe = chain.get(i);
PEViewHolder.name.setText(pe.getName());
}
protected static class PEViewHolder extends RecyclerView.ViewHolder {
protected TextView name;
public PEViewHolder(View v) {
super(v);
name = (TextView) v.findViewById(R.id.txtName);
}
}
Interesting quote on the different notify methods you are using
public final void notifyItemRemoved (int position) Notify any registered observers that the item previously located at position has
been removed from the data set. The items previously located at and
after position may now be found at oldPosition - 1. This is a
structural change event. Representations of other existing items in
the data set are still considered up to date and will not be rebound,
though their positions may be altered. Parameters position : Position
of the item that has now been removed.
public final void notifyItemRangeChanged (int positionStart, int itemCount) Notify any registered observers that the itemCount items
starting at position positionStart have changed. Equivalent to calling
notifyItemRangeChanged(position, itemCount, null);. This is an item
change event, not a structural change event. It indicates that any
reflection of the data in the given position range is out of date and
should be updated. The items in the given range retain the same
identity.
Could you try and comment this block of code and check if it works
//Called by the ItemTouchHelper when the user interaction with an element is over and it also
// completed its animation
/*
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//update from where the action took place
mPEAdapter.updateChain(viewHolder.getLayoutPosition());
//clearView is called after onMove so any drags or swipes are complete
dragging = false;
mPEAdapter.setSwipe(false);
}
*/
I am using the RealmRecyclerView from this post:https://realm.io/news/android-realm-listview/
In this ToDo app, when item is swiped, it is automatically deleted from RecyclerView as well as Realm Database.
I want to get notified when an item is deleted, also which item is deleted so I can perform some action with that item.
I tried using the Realm Change Listener, but that is invoked every time when a Realm Transaction is committed. So it is invoked even when a new item is added.
How do I do this? Is it possible with normal RecyclerView?
At this moment (with version V1.1.0), Realm does not provide a callback when a RealmObject is deleted. This is true for all types of data/views (listView, RecyclerView, RealmObject, RealmResults etc), also true if you query data asObservable or use a change listener.
However it does make sense to send an empty object, when the queried object is deleted from Realm, but since its a breaking change in Realm, we will have to wait for V2.0.
More details - https://github.com/realm/realm-java/issues/3138
According to the answer, they have not yet added this feature. But if you want to achieve this, you can use normal ReacyclerView using the existing RealmAdapter and everything as it is.
Here's how to do it:-
Remove the RealmRecyclerView and add the normal RecyclerView:-
1. Add the normal RecyclerView from the support library.
2. Initialize the recyclerview with the existing adapter i.e the adapter class that extends RealmBasedRecyclerViewAdapter, no need to make a new adapter
recyclerView=(RecyclerView)findViewById(R.id.realm_recyeler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new FilterAdapter(this,results,true,true);
recyclerView.setAdapter(adapter);
3. Next, we will use the ItemTouchHelper class to implement the swipe to dismiss for the RecyclerView :-
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView,RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.remove(viewHolder.getAdapterPosition(),alarmintent);
}
};
I've made a method in my adapter to remove item(shown below), you can do it here as well
The viewHolder.getAdapterPositon() gives the position of item swiped, it is passed to delete the RealmObject from Realm DB at given position(shown below)
0 -> drag flag - since I am not implement drag to move items, I've kept it as zero
ItemTouchHelper.RIGHT - swipe flags - These say in which direction the swipe to dismiss is set
ItemTouchHelper.RIGHT - swipe in right direction to dismiss
ItemTouchHelper.LEFT - swipe in left direction to dismiss
To support both directions, pass- ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recyclerView);
Create a new ItemTouchHelper object with above callback
Attach the ItemTouchHelper to RecyclerView
4. Here's how to remove the item (below is the code of my remove method of adapter):-
public void remove(int position)
{
RealmConfiguration configuration = new RealmConfiguration.Builder(context).deleteRealmIfMigrationNeeded().build();
realm = Realm.getInstance(configuration);
realm.beginTransaction();
realmResults.deleteFromRealm(position);
realm.commitTransaction();
notifyItemRemoved(position);
}
deleteFromRealm method is used to delete item at given position
call the notifyItemRemoved(position) to indicate item is removed at specified position
5. That's it, very easy and no need to create new adapters etc.
While Realm may not provide a callback for when an item is deleted, there is a way to know when items are deleted when using the RealmRecyclerView from this post.
The adapter (RealmBasedRecyclerViewAdapter) will call onItemSwipedDismiss(int position) whenever an item is swiped for dismissal. In your subclass of this adapter, you can override this method to add some extra logic.
For example, in my Recycler View, I want to give users the option to Undo a deletion. So, I override onItemSwipedDismiss(int position) and access the fields of the object being deleted. (In my case, this object is fairly small -- only three fields -- so this isn't too unwieldy). Then I call the super method: super.onItemSwipedDismiss(position); which will animate the deletion and remove it from Realm.
Then, I create a Snackbar with an action that re-creates the Realm object from the saved fields. Once it's created, it immediately goes back into the recycler view.
Here's a skeleton of the implementation of this method override:
#Override
public void onItemSwipedDismiss(int position) {
// Gather the object's fields, if you want:
YourObject objectToDelete = realmResults.get(position);
final String title = objectToDelete.getTitle();
final long timestamp = objectToDelete.timestamp;
// Perform delete and animation:
super.onItemSwipedDismiss(position);
// Add code here depending on what you want to do
// (for example, you could add a Snackbar that undoes
// the deletion by "resurrecting" your deleted object)
}
Here's my onBindViewHolder method which runs for every row in a recyclerView. I have around 500 to 600 rows at once. What Im doing is
1 - Initializing my Parcelable POJO class object
2 - Passing this object as an Intent argument to start another activiy when onItemClick event occurs.
AFAIK the Java doesn't support true closure so, It forces us to make an object/variable final if you want to use it in an inner class. Java imitate closure behavior by storing constant values in hidden variables and refer those later when needed.
What I want to know,
Is It fine in terms of performance/memory to create an object for each row in onBinderViewHolder method ?
When these objects get garbage collected ?
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Cursor cursor = getItem(position);
final FF_Task_List task = new FF_Task_List();
task.setSiteArea(cursor.getString(TaskListEntry.TaskListQuery.SITE_AREA));
task.setSiteCode(cursor.getString(TaskListEntry.TaskListQuery.SITE_CODE));
task.setValidOn(cursor.getString(TaskListEntry.TaskListQuery.VALID_ON));
task.setScore(cursor.getFloat(TaskListEntry.TaskListQuery.SCORE));
task.setSurveyStatusId(cursor.getInt(TaskListEntry.TaskListQuery.SURVEY_STATUS_ID));
task.setTokenId(cursor.getLong(TaskListEntry.TaskListQuery.TOKEN_ID));
task.setEmpId(cursor.getLong(TaskListEntry.TaskListQuery.EMP_ID));
task.setSurveyTypeId(cursor.getInt(TaskListEntry.TaskListQuery.SURVEY_TYPE_ID));
final int scoreTextLength = String.valueOf(task.getScore()).length();
final float score = task.getScore();
final int scoreColor = Utilities.getColorByStatus(mContext, task.getSurveyStatusId());
final int statusIcon = Utilities.getIconByStatus(task.getSurveyStatusId());
final String validOnDate = Utilities.formatDBDate(task.getValidOn(), Utilities.Dates.DISPLAY_DATE);
sb = new SpannableStringBuilder(task.getScore() + "%");
sb.setSpan(new StyleSpan(Typeface.NORMAL), scoreTextLength, scoreTextLength + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
holder.siteName.setText(task.getSiteArea());
holder.siteCode.setText(task.getSiteCode());
holder.siteInfo.setText(validOnDate);
holder.siteScore.setTextColor(scoreColor);
if(score != 0) {
holder.siteScore.setPadding(5,0,0,0);
holder.siteScore.setText(sb);
}else{
holder.siteScore.setText("--");
}
holder.siteScoreProgress.setPercent(score / 100);
holder.siteStatusIcon.setImageResource(statusIcon);
holder.taskItem.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
//raise custom itemClick event
itemClickListener.itemClicked(task ,v);
}
});
}
Since you have your model stored in a list you will get the value every time you call getItem(position) inside the onBindViewHolder(ViewHolder holder, int position).
The RecyclerView controls what are kept in memory at any time and so when you scroll the RecyclerView things (views and models) will get garbage collected when it's time for it.
I don't know how many items Android will keep in memory for you at any given time, but it's no more than the items that visible on the screen - and then it might keep a few items outside of view created. On a phone this would usually be 10-15 items, depending on the screensize and item's size.
Then when you scroll back to the same views again, the model will be recreated again and the ViewHolder will be updated with the newly created values.
Unless your data model is huge, it shouldn't be a problem creating the objects inside the onBindViewHolder(ViewHolder holder, int position).
If you want to avoid creating objects inside the onBindViewHolder convert the Cursor to your FF_Task_List objects and set the list of FF_Task_List to your backing model of the RecyclerView.
I have a listview in my android program that gets its information from an ArrayList adapter.
I have three methods that call listview.invalidateViews().
Two of these methods work without fail, and the third seems to freeze the listview. The information is correctly saved when backing out of the activity and on a screen rotate. But without taking these actions, the listview does not update.
Any Ideas?
UPDATE:
These instances work:
public void onItemClick(AdapterView<?> a, View v, int index, long id) {
al.remove(index);
adapter.notifyDataSetChanged();
}
public void addToList(View view) {
EditText et = (EditText) findViewById(R.id.ListText1);
if (et.getText().toString().equals("")) {
//do nothing
}
else {
al.add(et.getText().toString());
adapter.notifyDataSetChanged();
et.setText(null);
}
}
This method does not work:
public void resetList(View view) {
al = new ArrayList<String>();
adapter.notifyDataSetChanged();
}
you are using invalidateViews() differently, if you want to change the view of the listview's child then you can use the invalidateViews() but if you are changing the data/content of the listview's adapter you need to use notifyDataSetChanged() method.
the difference of the two are discussed in this question
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.
Adapter.notifyDataSetChanged()
on the other hand, is to tell the observer of the adapter that the contents of what is being adapted have changed. Notifying the dataset changed will cause the listview to invoke your adapters methods again to adjust scrollbars, regenerate item views, etc...
and with your method
public void resetList(View view) {
al = new ArrayList<String>();
adapter.notifyDataSetChanged();
}
making a new object of ArrayList<String>(); will not reset the data of your list view. just because the al ArrayList that you passed on your adapter is now different to your al = new ArrayList<String>(); what you need to do now is to access your current arraylist then clearing its content with al.clear() method.