My application is returning the latest data from firebase to the buttom of the ListView. But I want it to be on the top! I have thought about it and I think there is only two possible ways to do it.
1. Invert the Listview.
I think that this way is how it should be done but I couldn't figure it out. I have searched a lot on the web but no suitable solution for my case
This is my adapter code
public void onStart() {
super.onStart();
// Setup our view and list adapter. Ensure it scrolls to the bottom as data changes
final ListView listView = getListView();
// Tell our list adapter that we only want 50 messages at a time
mChatListAdapter = new ChatListAdapter(mFirebaseRef.limit(50), this, R.layout.chat_message, mUsername);
listView.setAdapter(mChatListAdapter);
}
And this is the code for the ChatListAdapter constructor for a custom list class ChatListAdapter which extends special list adapter class FirebaseListAdapter:
public ChatListAdapter(Query ref, Activity activity, int layout, String mUsername) {
super(ref, Chat.class, layout, activity);
this.mUsername = mUsername;
}
[Edit] This is some of the code for FirebaseListAdapter which extends BaseAdapter class
public FirebaseListAdapter(Query mRef, Class<T> mModelClass, int mLayout, Activity activity) {
this.mRef = mRef;
this.mModelClass = mModelClass;
this.mLayout = mLayout;
mInflater = activity.getLayoutInflater();
mModels = new ArrayList<T>();
mModelKeys = new HashMap<String, T>();
// Look for all child events. We will then map them to our own internal ArrayList, which backs ListView
mListener = this.mRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
T model = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(dataSnapshot.getKey(), model);
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(model);
} else {
mModels.add(nextIndex, model);
}
}
}
2. Descending query the data.
The second way seams impossible to me, because when I searched on Firebase API documentation and on the web, I couldn't find anyway to order retraived data on descending way.
My data on firebase look like the following:
glaring-fire-9714
chat
-Jdo7-l9_KBUjXF-U4_c
author: Ahmed
message: Hello World
-Jdo71zU5qsL5rcvBzRl
author: Osama
message: Hi!
Thank you.
A simple solution would be to manually move the newly added data to the top of the listview. As you rightly noticed, new data added to a listview will automatically be appended to the bottom of the list, but you may freely move entries once they are added. Something like the following would help you manually move the newest entry to the top of the list:
int iSwapCount = listView.getCount() - 1;
int iPosition = listView.getCount() - 1;
for (int j = 0; j < iSwapCount; j++)
{
Collections.swap(yourlistobject, iPosition, iPosition - 1);
iPosition = iPosition - 1;
}
The above code will begin by calculating the number of swaps that will be required to move last list entry to the top of the list, which is determined by the number of elements in the list - 1. The same is true for calculating the last position in the list. From there Collections.swap will be used to swap the last element in the list with the element before it; this will be repeated until the last element is now the first element, with the rest of the entries in the list remaining in the same order. This code would have to be called each time a new entry is added so that the overall order of the list is maintained.
I realize it has been a while since you asked but I had the same issue. It does not appear that there is a direct answer here.
Here's the change to the firebase adapter to get new items on the top of the list.
Notice the change from add(...) to add(0,...) and add(next...) to add(prev...)
Look for comments:
// prepend instead append
Example:
...
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
mKeys.add(0, key);
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
//mModels.add(model);
//mKeys.add(key);
// prepend instead append
mModels.add(0,model);
mKeys.add(0,key);
} else {
//mModels.add(nextIndex, model);
//mKeys.add(nextIndex, key);
// prepend instead append
mModels.add(previousIndex, model);
mKeys.add(previousIndex, key);
}
}
...
Here is a simple way to invert a FirebaseUI list using a RecyclerView:
boolean reverseList = true;
LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseList);
if (reverseList) {
manager.setStackFromEnd(true);
}
mRecyclerView.setLayoutManager(manager);
Related
I've implemented a recyclerView with a drag and drop feature in my app. Everything works fine until the app is relaunched --any drag and drop changes were not saved/remembered by the app.
I've tried:
Using SharedPreference + GSON
Reading other SQLite answers here on SO like this one: Store new position of RecyclerView items in SQLite after being dragged and dropped
Reading Paul Burke's Medium Post
My current code looks like this:
In onCreate
ItemViewModel itemViewModel = ViewModelProviders.of(this).get(ItemViewModel.class);
itemViewModel.getAllItems().observe(this, new Observer<List<Item>>() {
#Override
public void onChanged(List<Item> items) {
adapter.setItemList(items);
itemList = items; //Global variable itemList
}
});
The method called when item is dragged/moved
private void swap(int firstPosition, int secondPosition) {
Collections.swap(itemList, firstPosition, secondPosition);
for(int i = 0; i < itemList.size(); i++) {
Item currentItem = itemList.get(i);
ItemViewModel.update(currentItem );
}
adapter.notifyItemMoved(firstPosition, secondPosition);
}
Any ideas how I can let my app save the reordered recyclerView after drag and drop? Thanks in advance.
If you wish to preserve the order of your items once you close your app or move to another activity, you will need to add a property to your entity files which will server as an order value for it. Once added, you could access your dao and run an UPDATE SQL in the Collections.swap method when reordering items and than on every load of the list, return the items ordered by that property.
So, I'm making a screen with Leanback's Browse Fragment and CardPresenter.
Inside my fragment that extends BrowseFragment, I have a method for drawing the UI:
private void loadCardRows() {
mRowsAdapter = new CustomArrayObjectAdapter(new ListRowPresenter());
final List<UiType> uiTypeList = new ArrayList<>(uiTypes);
for (UiType uiType : uiTypeList) {
HeaderItem cardPresenterHeader = new HeaderItem(0, uiType.getName());
List<TypeReportItem> items = performUiTypeFiltering(uiType.getEndpointType());
CardPresenter cardPresenter = new CardPresenter(attributesHelper);
CustomArrayObjectAdapter cardRowAdapter = new CustomArrayObjectAdapter(cardPresenter);
for (TypeReportItem item : items) {
cardRowAdapter.add(item);
}
mRowsAdapter.add(new ListRow(cardPresenterHeader, cardRowAdapter));
}
setAdapter(mRowsAdapter);
}
Now I'm having a service that loads some data every few seconds. That data is reachable through attributesHelper that I'm passing to CardPresenter.. How am I supposed to reload that data without causing the screen to blink every few seconds?
mRowAdapter.notifyArrayItemRangeChanged(startingIndex, mRowAdapter.size());
Starting index is the one from which position your are updating data.Dont give starting index something like 0,which will result in screen blink from index 0
Maybe this anwser will be helpful
for (int i = 0; i < mAdapter.size(); i++) {
ListRow listRow = ((ListRow) mAdapter.get(i));
ArrayObjectAdapter listRowAdapter = ((ArrayObjectAdapter) listRow.getAdapter());
if (listRowAdapter.size() > 0) {
listRowAdapter.notifyArrayItemRangeChanged(0, listRowAdapter.size());
}
}
android tv -Reloading adapter data
I have used this code in my project. No blink happened.
notifyArrayItemRangeChanged
caused blinking, so try this Adapter
class RefreshableArrayObjectAdapter(presenterSelector: PresenterSelector) :
ArrayObjectAdapter(presenterSelector) {
fun refresh() {
notifyChanged()
}
}
I have an array list that displays a list of companies. Each of these companies is having corresponding town names. Further, I am creating buttons for each of the town names such that when I click on any of the town names, the ArrayList should be filtered and only the companies with that town name should be displayed.
The buttons are like,
stringList.add(tempList.get(n).getTownName());
btnTag.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 60));
btnTag.setText(stringList.get(k));
btnTag.setBackgroundResource(R.drawable.alpha_button_selector);
btnTag.setClickable(true);
townLayout.addView(btnTag);
On clicking the button, I am fetching the text in the button as it displays the town name and then I am calling the method to filter the data,
btnTag.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
CharSequence name = btnTag.getText();
locationFilter(name);
}
});
For filtering the data, I am creating a new ArrayList to store the filtered data such as,
ArrayList<CompanySearchResult> filtered = new ArrayList<CompanySearchResult>();
for (CompanySearchResult town : tempList) {
if (town.getTownName().equals(name)) {
filtered.add(town);
}
}
tempList.clear();
tempList.addAll(filtered);
listAdapter.notifyDataSetChanged();
The data is getting filtered properly, but my issue here is when I filter the data for the first time, my tempList(which is the main ArrayList containing the data) gets updated by the contents of the filtered ArrayList.
Then when I click on the second button with another town name and try to filter that, it tries to filter the tempList which is already updated with the filtered data and the result displayed is blank.
Thus, how can I populate my tempList with the original data again each time a button with the town name is being clicked and then filter it out in a similar manner.
I have researched on this but couldn't find anything relevant. I have also tried the solution mentioned in the link:
android filter custom array adapter and bring back old items again
How can i take a backup of an ArrayList in Java after i call .clear()?
However, it didn't help. can anyone please help on how can I achieve this?
#Traxdata answer is correct. Here is my explain in code
Modify your adapter like
public class YourAdapter extends RecyclerView.Adapter {
YourAdapter(List<CompanySearchResult> filterList){
}
}
In your Activity
YourAdapter listAdapter = new YourAdapter(filterList);
At first time you display the list
filterList = new ArrayList<>(tempList);
listAdapter.notifyDataSetChanged();
When you filter
ArrayList < CompanySearchResult > filtered = new ArrayList < CompanySearchResult > ();
for (CompanySearchResult town: tempList) {
if (town.getTownName().equals(name)) {
filtered.add(town);
}
}
filterList.clear();
filterList.addAll(filtered);
listAdapter.notifyDataSetChanged();
You need to store the original set of items in the adapter as a backup list.
The adapter should always only display the contents of the filtered list.
If you add data to the adapter, add them to the backup and filtered list.
When you click a button, clear the filtered list and then add only the matching items from the backup list to the filtered list.
public class SomeAdapter extends RecyclerView.Adapter<SomeAdapter.SomeViewHodler> {
static class SomeViewHodler extends RecyclerView.ViewHolder {
public SomeViewHodler(final View itemView) {
super(itemView);
}
void bind(String data) {
// FIll views here
}
}
private final List<String> displayedList = new ArrayList<>();
private final List<String> originalList = new ArrayList<>();
private String filterString = "";
#Override
public int getItemCount() {
return displayedList.size();
}
#Override
public void onBindViewHolder(final SomeViewHodler holder, final int position) {
holder.bind(displayedList.get(position));
}
#Override
public SomeViewHodler onCreateViewHolder(final ViewGroup parent, final int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.view, parent, false);
return new SomeViewHodler(view);
}
/**
* Call from outside to send data into this adapter
*/
public void replaceData(#NonNull final List<String> data) {
// Store new data in original list
originalList.clear();
originalList.addAll(data);
filter();
notifyDataSetChanged();
}
private void filter() {
// Clear display first
displayedList.clear();
// If filter is empty, show all items
if (filterString == null || filterString.isEmpty()) {
displayedList.addAll(originalList);
} else {
// if filter is not empty, check if item matches query
for (String obj : originalList) {
if (obj.contains(filterString)) {
displayedList.add(obj);
}
}
}
}
}
Create one more list(eg.mainList) where you created tempList and put tempList into that like this
mainList.addAll(tempList)
and then update this method like this
ArrayList<CompanySearchResult> filtered = new ArrayList<CompanySearchResult>();
for (CompanySearchResult town : mainList) {
if (town.getTownName().equals(name)) {
filtered.add(town);
}
}
tempList.clear();
tempList.addAll(filtered);
listAdapter.notifyDataSetChanged();
Basically I have a spinner where you can sort your listview, the code i am using for sorting:
if(arg2==0)
{
Sorting_class.QueueSort(mList);
}
else if(arg2==1)
{
Sorting_class.AlphaSort(mList);
}
After sorting and logging the list, it looks sorted without any problem! But as soon as I call adapter.notifydatasetchanged it messes up, for example it overrides the info of the last element with the first element.
If my objects has a string name like this:
z
s
a
after calling alpha sort it looks like this from the logcat:
a
s
z
but after calling adapter.notifydatasetchanged to display the new info, it looks like this:
a
s
a
and it keeps on doing this after each sort until all elements get the same info. After sorting for the second time, my listview looks like this:
a
a
a
this is the code i am using for the sorting:
public static void QueueSort(ArrayList<item_base> mList)
{
Collections.sort(mList, new Comparator<item_base>() {
#Override
public int compare(item_base lhs, item_base rhs) {
return lhs.GetTimeMil() < rhs.GetTimeMil() ? -1 : 1;
}
});
}
public static void AlphaSort(ArrayList<item_base> mList)
{
Collections.sort(mList, new Comparator<item_base>() {
#Override
public int compare(item_base lhs, item_base rhs) {
return lhs.getmName().compareTo(rhs.getmName());
}
});
}
this is the top part of the getview function " it's very long "
if(convertView == null)
convertView = getActivity().getLayoutInflater().inflate(R.layout.listview_shopping, parent,false);
final item_base item = mList.get(pos);
Log.d("sorting" , "getview = " + item.getmName() + " pos = " + pos);
the only solution I found is to set the adapter again after each sort:
mAdapter = new Listview_customAdapter(getActivity(), mList, R.layout.listview_shopping);
mListView.setAdapter(mAdapter);
sort before you set the adapter
I mean after here:
if(arg2==0)
{
Sorting_class.QueueSort(mList);
}
else if(arg2==1)
{
Sorting_class.AlphaSort(mList);
}
//Set the adapter here (after sorting has occured)
mAdapter=new Listview_customAdapter(getActivity(),mList,R.layout.listview_shopping);
mListView.setAdapter(mAdapter);
According to your comment that this will affect the performance of the adapter:
If you are using recycle() view practice, that won't affect the perfromance that much.
So in getView() function:
instead of:
View rootView = LayoutInflater.from(context)
.inflate(R.layout.banana_phone, parent, false);
if you use:
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.banana_phone, parent, false);
}
ListView recycles the views that are not shown any more, and gives them back through convertView
For further details you can read here
You need to create two ArrayLists.
So when you use the sort algorithm you are creating a second ArrayList and not overwriting the original ArrayList.
ArrayList list1 and ArrayList list2.
copy the values of list1 into list2.
Then perform any sorting on list2.
Depending on whether the user selects the sorted list or the original list, set your adapter with the selected list. This means you alter the list in your adapter and then set it each time a different sort is selected.
If you are concerned about setting the adapter each time. You can see another way to sort the items of your adapter bypassing this in this code, which comes from this answer here.
I am not sure if this a more cost effective way to achieve what you are wanting to achieve. That choice is ultimately yours.
I am working on a XMPP based chat in android.. and I am struck at a point where I need to update the position of an item in the listview to the top in case a new.message arrives.
The use case is.. I am on Contacts screen of the app and a new message comes.. so this contact should move to top of the list and get bold. This is what is similar to whatsapp as well
How can this be done. My class imolemebts activity and i have implemented custom list adapter.
So howcan I find if an item exists in the listview and secondly how to dynamically change position
First, keep in mind that a ListView is just a representation of a list of Objects. So if you want to know if an item is in the ListView, you just have to check if the corresponding Object is in your list of Objects.
Is the same when you want to change the position of one item, you have to change the position of the Object in the list.
Start by defining these objects:
private ArrayList<MyObject> lists = new ArrayList<MyObject>();
private MyCustomAdapter myAdapter;
The first time you create your ListView, just do as usually:
//fill your list with your objects
lists.add(myObject1);
lists.add(myObject2);
lists.add(myObject3);
//create and set the adapter
myAdapter = new MyCustomAdapter(..., ..., lists);
myListView.setAdapter(myAdapter);
Now you can know if your lists contains a specific object (which is the same that checking if an item is in your ListView) by simply testing that:
lists.contains(anObject);
Then, if you want to change the position of a specific item in the ListView, you have to create a new list and put the elements in the correct order. You can use something like that (not tested but it should work):
private ArrayList<MyObject> moveItemToTop(ArrayList<MyObject> lists, int positionOfItem) {
if (lists == null || positionOfItem < 0 || positionOfItem >= lists.size()) {
return lists;
}
ArrayList<MyObject> sortedList = new ArrayList<MyObject>();
//add the item to the top
sortedList.add(lists.get(positionOfItem));
for (int i=0; i<lists.size(); i++) {
if (i != positionOfItem) {
sortedList.add(lists.get(i));
}
}
return sortedList;
}
Or even this (which is way easier...).
Finally, call these two methods to update your ListView:
myAdapter = new MyCustomAdapter(..., ..., moveItemToTop(lists, itemPosition));
myListView.setAdapter(myAdapter);
This is how I resolved it
private void moveMessageToTop(MessageObject message) {
int index = 0;
for (Friends friend : mFriends) {
if (friend.getName().equalsIgnoreCase(message.getFrom().split("#")[0])) {
index = mFriends.indexOf(friend);
break;
}
}
if (index != 0) {
mFriends.add(0,new Friends(message.getFrom().split("#")[0], message
.getMessage()));
} else {
Friends frnd = mFriends.get(index);
frnd.setStatus(message.getMessage());
mFriends.add(0, frnd);
mFriends.remove(index);
}
((ListAdapter) lvFriends.getAdapter()).notifyDataSetChanged();
}