I already have a list adapter that works properly. But I want to divide the object in the list into sections according to the date they were created. Something like this:
I found something called "sectioned recycler view" but couldn't find any documentation on that. I read all the related questions, but they all are either outdated or use a third-party library. What's the native way of implementing this feature?
There are a couple of approaches you could use. First the easy one:
make the header part of your item layout, but with GONE visibility by default
in onBindViewHolder, decide whether the header should be VISIBLE or GONE
The logic there depends on what you want, but it could be as simple as
val visible = position == 0 || items[position].date != items[position - 1].date
Basically you just need to work out what the condition is that would cause an item to be in a different "group" than the previous item, and then if it's met, show the header over that item.
The approach MarkL is talking about is more complex, but it's also more robust - by having separate Item and Header elements, you can treat them differently, and even do stuff like having the header collapse/show its children, select them all etc. You can do that with the other approach, but it requires more code since you're not really treating things as groups, it's more of a trick when it comes to displaying stuff.
Basically, ignoring the how for now, you need a list of headers and items. A sealed class is a good way to represent that:
sealed class ListElement {
data class Header(val date: Date) : ListElement()
data class Item(val itemData: YourItem) : ListElement()
}
I've made Item a wrapper class that holds your actual data object inside, since that's probably coming from elsewhere and you can't define it as part of this sealed class hierarchy - so sticking it inside one of the subclasses like this allows you to do that.
So now you can have a List<ListElement> containing Headers and Items in display order. Since you've mentioned creating the ViewHolders in a comment I won't explain all that, but basically when you're getting the item type for a position, you just need to check is Header or is Item and then handle it from there.
As for creating that list, there are lots of ways to do it - you could use groupBy to generate a Map of dates to lists of items, map each of those entries to a list of Header, Item, Item..., and flatten the whole thing into a single list:
items.map { Item(it) }
.groupBy { it.itemData.date }
.entries
.flatMap { (date, items) -> listOf(Header(date)) + items }
The advantage with a map like this is it's an actual grouped structure, so you can keep it around to generate flat lists for display - e.g. hiding a group's contents by only including the header in the list.
Or you could just build the list yourself, similar to the logic I mentioned in the first example - if the date has changed from the previous item, insert a Header first:
val list = mutableListOf<ListElement>().apply {
for (item in items) {
// add a header if the date changed - this handles the first header
// in an empty list too (where lastOrNull returns null, so the date is null)
val previousItemDate = (lastOrNull() as? Item)?.itemData?.date
if (previousItemDate != item.date) add(Header(item.date))
add(Item(item))
}
}
Or you could use fold. Don't forget to sort stuff!
You could create 2 types of view holders:
header which holds the date
data container which holds the other information.
And then create a list of objects which contain something like this:
listToBind = (header, data, data, header, data, data)
For your case, where header & info is the same object, you can do something like this:
take your object you receive from backend (example)
YourObject(val header: String, val info:InfoObject)
create 2 display objects, both inheriting from a type that your adapter accepts (say - AdapterDisplayEntity)
HeaderDisplayEntity(val header: String): AdapterDisplayEntity
InfoDisplayEntity(val info: InfoObject): AdapterDisplayEntity
now you can use your list that contains these items and submit to your adapter.
Use nested recycler view for this instead. You can check the example here.
Best solution for this scenario so far.
If you are using Jetpack Compose you can use the stickyHeader() as documented in the documentation
Related
Context
So, I don't know if any of you has ever gone through the same situation but I've recently taken over an app and we have this RecyclerView on the main screen - because of an NDA I'll change a few things - that shows a list of apartments that you can rent - picture the AirBnB app - and if you tap on one of these apartment items you go to the apartment detail, where you have a bit more of functionality and features.
The thing is that we have way too many moving parts on the apartment list. For example, on each apartment ViewHolder you can:
Use a checkmark to specify if you are going to bring any pets with you.
A few UI items to specify how long are you going to stay.
An EditText to set how may people are going to come.
A Rent button that turns itself into a spinner and sends an API call.
A More Options button that expands the ViewHolder, showing a LinearLayout with yet more UI.
Picture something like this
This is actually a simpler example of what I really have. Let me tell you that it looks as if each ViewHolder could be a Fragment because of all the functionality that we have on each.
Now what's the problem here?
Recycling issues. If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right? If you had checked a CheckButton that's supposed to be check. If you had written something on an EditText, that's supposed to be there. If you had expanded the More Options section, that's supposed to be expanded. You see where I'm going at?
What am I asking here?
Well, about feedback for a possible solution or improvement. I know what most of you would tell me here - because it is the same thing I thought at first - just move all that functionality into the apartment detail, keep that list as simple as possible. But it is not as simple, we have a large user base who is already used to this UI. Changing things so abruptly is not an option.
What do I have right now?
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex. This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
Notes
Sorry I haven't provided any code, but there's not much to show actually, as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects. I save these "State" objects via the onViewDetachedFromWindows() hook from the adapter class that gets triggered when a ViewHolder scrolls off from screen. I wipe out these "State" objects when I fetch a new API response.
Any feedback is appreciated,
Thanks!🙇
Your post is vague in it's high-level description but I'll try to comment in a similar manner that may guide you towards solutions.
First, as was already mentioned Epoxy is a thing. As is adapter delegates. You may find those useful. However, you don't need a library to solve you problem - you need separation of concerns and architecture.
The thing is that we have way too many moving parts on the apartment list.
OK, so first suggestion is to stop having too many moving parts in the list. Each thing you listed could / should be it's own (custom) view that is driven by it's own ViewModel. A recycler view / view holder / adapter should be as stupid as possible. All those things should be doing is filling in boilerplate that Android requires. Actual logic should exist elsewhere.
If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right?
No. Your ViewHolder should not maintain state. A ViewHolder holds views so Android doesn't have to re-inflate stuff over and over. It should not keep track of its state - it should be told what its current state is.
You should have a list of data objects (view models) that represent the current state of each item in the list. When you scroll off and back to the same position, you are supposed to re-bind the item that should be at that position to the view that represents it. Saving and clearing "state" objects should not be necessary - you should always have the current state on hand because it's the underlying data model driving your whole UI.
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex
If something is too big and complex, break it down. Instead of having one giant-ass state object for each item, use composition. Make this item state have properties that represent pieces of the UI - PetModel, DateRangeModel, etc.
This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
That does sound crazy because not only would this not solve your problem, you would probably actually make it significantly worse. You don't want to manage the state of a bunch of ViewHolders but you want to manage the states of a bunch of Fragments!? Bruh.
as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects.
Again, break that up. You should not be slapping "data I fetched from the API" directly onto views. Invariably you will need to massage and transform raw data from an API before you display it. This should be handled by a dedicated object (again, ViewModel or some other structure). Again, views should be dumb. Tell them their state and that's it - don't do logic at this level.
Please read the Android Architecture Guide.
Also Google around for "Clean Architecture" - that seems to be all the range in Android these days.
And finally - here's some very rough pseudocode of how you could structure this to be more testable and maintainable.
From the bottom up:
ApiClient - responsible for just fetching the raw data from the API
endpoint or reporting an error.
ApiResponseModel - language-specific object representation
of the data you'll get from the API. Has info on the pet, dates,
guest count, etc. May contain submodels.
ItemDomainModel - client side representation of your data after transforming the data you'll get from the API.
Repository - uses the ApiClient to fetch the data as ApiResponseModel and transforms it into a ItemDomainModel object that makes more sense for your app.
ItemViewModel - Represents the UI state of a single item in the RecyclerView. Takes a ItemDomainModel instance and exposes the state of the UI based on the state of that model. This can be broken down if it's too complex (PetStateViewModel, DateRangeViewModel, GuestCountViewModel, etc)
ListViewModel - The top-level Android ViewModel that represents the state of the screen. Uses the Repository to fetch the data then constructs a list of ItemViewModels to feed into the RecyclerViewAdapter.
If you get those pieces in place, your view binding in the adapter should be stupid dumb:
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// The adapter list should be a list of view models populated by the
// fragment after the ListViewModel returns a list of them from the fetch
val itemViewModel = itemViewModels[position]
// Populating this item view should just be a one-to-one mapping of the view model
// state - NO LOGIC. Dumb. Stupid. Tonto.
viewHolder.bringingPets.isChecked = itemViewModel.isBringingPets
viewHolder.guestCount.text = itemViewModel.guestCount
// ... etc, etc (if you use databinding this is a one-liner and even stupider)
// Set up your event listeners so interacting with this specific item in the list
// updates the state of the underlying data model
viewHolder.bringingPets.setOnCheckChanged { itemViewModel.isBringingPets = it.isChecked }
viewHolder.rentButton.onClickListener { itemViewModel.rentThis() }
// ... etc, etc
}
The goal is to do as little as possible here. Just update the state and wire up your callbacks that just delegate back to the ViewModel. Then, those UI states are driven by the logic in the view model. This is where you do business logic that determines how the UI should look.
class ItemViewModel(private val dataModel: ItemDomainModel) {
var isBringingPets: Boolean
get() = /* some business logic that determines if the checkbox is checked */
set(value) /* update underlying state and notify of changes */
// ... etc, etc, for guest count and other properties
fun rentThis() {
// Fire an event or update live data or invoke a callback that
// the fragment can use to respond
}
// ... etc, etc, for other functions that respond to UI events
}
In Summary
Refactor your code to break down the huge and complex logic into dedicated components that each have a simpler, specific focus, then compose them together to get the behavior you want. Good luck.
picture from a reddit news feed
(https://i.stack.imgur.com/6YXMK.jpg)
I am creating an app with a list view that is populated from a sqlite database. Each of the data base items can have a status of either “resolved” or “unresolved”.
I want the listview to have 3 “tabs” with the labels “all items”, “resolved items”, and “unresolved items” with correspoding sqlite queries to populate each.
It should behave similarly to the one pictured.
I assumed this would be a tabbed listview and have been watching tutorials for a week based on those search words and it’s taking taking me down a dark rabbit hole of fragments and changing gradles and so on. I’m not sure tabs are what i really want.
Could I do this with three buttons instead where each button would run a different query and populate my listviewcontainer?
Ideally, when the page is opened, the first “tab” would be highlighted and the listview populated with all records. As the other tabs are pressed, they would highlight and a new query would run.
Would another approach work better?
I’m not asking for code, I just want some conceptual direction on where to focus my research.
If I get you right you need to filter your query results in different lists. Making a lot of queries into database is not the thing that is preferable specially if it's going to be a long process and doing it a lot of times is time and memory consuming.
So to make it work you could simple store your full query result in one variable and change the RecyclerView data using custom method setList() and later using notifyDataSetChanged() to apply the changes.
To make it work you need to get understanding of "how RecyclerView works" and then you will be fine.
So after providing the right logic you would be able to simple split your whole query result as it's needed (by element values for example) as it's showed above:
About the code below:
list - is your query result
leftFilterList or rightFilterList - are lists that contain sorted items
adapter.setList(rightFilterList) - sets the RecyclerView data (filtered items in our case)
adapter.notifyDataSetChanged() - is used to notify RecyclerView that list was changed, and he need to rebuild it.
So we have two Buttons and logic that fillter items in differend ways.
public void left(View view) {
ArrayList<ExampleItem> leftFilterList = new ArrayList<>();
for (ExampleItem item : list) {
if (item.getTitle().length() % 2 == 0) {
leftFilterList.add(item);
}
}
adapter.setList(leftFilterList);
adapter.notifyDataSetChanged();
}
public void right(View view) {
ArrayList<ExampleItem> rightFilterList = new ArrayList<>();
for (ExampleItem item : list) {
if (item.getTitle().length() % 2 == 1) {
rightFilterList.add(item);
}
}
adapter.setList(rightFilterList);
adapter.notifyDataSetChanged();
}
And the result of filtering*:
sorry for wrong toast text. It shows the whole list size.
I'm trying to create a custom View that contains a list of CheckBoxes based on my database. This means I have to create the Views at run-time, and can't do it in XML. However, the method I'm using for this is very slow. Is there a faster method to create large amount of Views in code?
For example, with the 18 types in my database, it can take over 1 second to create all the CheckBoxes.
class FilterView : LinearLayout {
private fun init(types : List<Type>){
... setup
// Creating the CheckBoxes, this takes all the time.
checkboxes = Array(types.size, {
AppCompatCheckBox(context).apply {
text = types[it].type
CompoundButtonCompat.setButtonTintList(this, ColorStateList(states, intArrayOf(colours[it], colours[it])))
}
})
... add to view
}
What your looking for is a Recyclerview. It can all be explained here. The downvote was likely because this is assumed to be common knowledge or easily googled on your own. I was new once too. Here you go.
I want to create a custom ListView.
Initially, the custom ListView has one array of data, but when user taps one of the list items, it's then removed from current array and added to another. If the user taps on the second array, the list item is then added back over into the first array.
Please suggest how to apply logic to do this.
Updates : I wants to use only one listview/recyclerview.
Following are screen shots..
Regarding the object switch - this is a simple transfer between lists, , just know beforehand if the insertion and removal is index based, e.g:
contacts.add(iLocation, ContactObject);
favorites.remove(iOtherLocation);
Regarding the ListView stuff, I would suggest converting to RecyclerView, let's build a general scenario:
You have a screen (Activity or Fragment) that holds one list (the implementation can be ListView or Recycler), and another screen that holds the other list.
In both of your lists you have adapters in which you implement the logic for the clicks on the objects in the lists.
The click transfers the object, either directly to the other list, OR to a temporary Object holder (because you might need it for other stuff), in which case you will need to pull that object from the other view, either way you remove it from the current one.
you switch to the other view, and refresh it.
An easy way to go -
Assuming the screens are the same, use only one Activity, holding a single RecyclerView, and handle 2 adapters, each for every list, the adapters allow you to handle the clicks easily, with an index for the object clicked, the click executes the info swap action,the Activity handles the visual swap Action.
a very general example would be:
//init everything obviously ;)
List<ContactObject> contacts;
List<ContactObject> favoritesContacts;
//the AdapteListener is an interface declared inside the adapter
mContactsRecyclerAdapter = new ContactsRecyclerAdapter(this, contacts,new ContactsRecyclerAdapter.AdapterListener()
{
#Override
public void cellClicked(int iIndex, ContactObject object)
{
favoritesContacts.add(iIndex, ContactObject);
contacts.remove(iIndex);
mContactsRecyclerAdapter.notifyDataSetChanged();
mFavoritesRecyclerAdapter.notifyDataSetChanged();
mRecyclerView.swapAdapter(mFavoritesRecyclerAdapter, false);
}
});
And vice-versa for the other adapter.
Hope this helps, comment if you have problems and I'll update.
Please implements with custom view extend with Linearlayout
Custom view has 2 child Linearlayout in which will add with this custom view
First time add all the element in first Linearlayout and based on user action please remove from first Linearlayout and add it in another layout
I'm looking for a view known as LongListSelector on the Windows Phone. It's a list view with group headers. Tapping a group header displays only a list of groups. Tapping a group on the list of groups hides the list of groups and scrolls the view to the selected group. It's a very useful way of groupping long lists with easy navigation between groups. If there are alternatives fit for the same purpose that would be also great.
You can do this easily :)
The first thing you need to do is make sure your data source is a collection of collections. I would suggest an ObservableCollection> if you want maximum binding goodness. Then we can construct our listView as follows:
var listView = new ListView ();
listView.SetBinding (ListView.ItemsSourceProperty, "Data");
listView.ItemTemplate = new DataTemplate (typeof (MyCell));
listView.GroupHeaderTemplate = new DataTemplate (typeof (MyHeaderCell));
listView.IsGroupingEnabled = true;
listView.GroupShortNameBinding = new Binding ("Title");
In order, we first bind in our data, I am assuming the BindingContext here will be inherited from the page. Our data should be the collection of collections already mentioned.
Then we bind in our ItemTemplate as normal, we make a GroupHeaderTemplate, this will be the template shown in the list during normal scrolling. Next we enable grouping to tell the list to use the data as a grouped collection rather than a flat list.
Finally with all that done, we provide a binding for the GroupShortName. This binding is run against the collection for each group to grab out a string (or an object that will have ToString called on it) to produce the jump list as you showed in your screenshots.
For performance reasons you may want to ensure the ItemsSource is not set until everything else has been set to avoid the ListView attempting to realizing Cells in a partially configured state. This will not actually result in bugs, it just forces the ListView to do more work.