B4A Library [Lib] UltimateListView

I've been working on this project for a long time and I'm very proud to release the version 4 today.

The UltimateListView is, as its pompous name says, THE ListView.

  • It can handle very long lists. This is a screenshot of a list with 245813 items, all different:

    verylonglist.jpg


  • It can mix different layouts (and they can be changed dynamically). You can use it as an expandable ListView:

    layouts.jpg


  • It has a low memory footprint and is very fast (this report comes from the Performance demo where the list has to display 128901 distinct words read from a database and the used device is a Huawei Honor single core 1.4 Ghz):

    performance.png


  • It can scroll in both directions thanks to its swipe detector:

    tables.jpg


  • The swipe detector can also be used to implement a swipe-to-dismiss or a swipe-to-reveal:

    swipedetector.png
  • You can easily add editors to your table to change its content:

    celledit.jpg


  • You can animate the items when they are added, removed, replaced or when the list is scrolled (with your own custom animation):

    animationclap.png


  • It can stack items from the bottom:

    stackfrombottom.png


  • It supports drag & drop operations (internal & external):

    dragndrop.png


  • You can synchronize lists with different item heights:

    grid.jpg
The examples will show you how to implement a Pull-to-Refresh, create sticky headers or combine several lists to make a wheel. One of the examples is an improved version of my File Explorer class.

All texts and images can be loaded asynchronously (from Internet, from a database or from a local folder), so you can scroll even if the data are not fully loaded.

The list has its own state manager.

Since September 2018, ULV is available for free. You can still donate for it if you wish.
To send the money, just click on the Donate button below (the amount to enter is in euros):


Note that UltimateListView is not a wrapper around the work of someone else. It is 100% my own code and it is based upon the standard Java ListView of Android.

The UltimateListView does not work with Android versions < 2. It cannot work with B4J or B4i.

Current version: 4.50

DOWNLOAD HERE:
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
Keeping both in sync is easy. First use a map and not an array (or convert your array to a map with a For loop), and define a key for each entry which will be the idemID required by ULV. If you remove an entry, that changes nothing for your map keys and nothing for the items ID.
The important thing to keep in mind is that ItemID is really an ID, as your driver license number or your identity card number. It doesn't have to change during the whole life of the application and is unique. Position is also unique but it is not a reliable ID because it may change. For example, when you sort items, all positions change but the IDs remain the same. Relying upon the position would be a programming mistake.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
There's a case where the IDs don't have to be unique: when they are assigned to identical items or static items. For example, if you add three items with the same views and the same content, there's no need to distinguish them so you can use the same ID for all three. If these items are not identical but they are static (their content will never change and is set when the views are created; their filler sub is empty), you probably don't care of their ID so you can assign them a bogus ID (e.g. 99999999).
 

Informatix

Expert
Licensed User
Longtime User
I could upload the project if you'd like to take a closer look. It's a small project
Unfortunately I cannot test your project because it needs a very long list of contacts, and it's not the case on my test devices (the fast scroller doesn't even appear). But a proper handling of your IDs is maybe the key to solve the problem. Let me know.

For support, I prefer to use emails because it's easier to exchange projects and screenshots.
 

Eduard

Active Member
Licensed User
Longtime User
Thanks for your help,

I sent a new version of the project to you by mail. I added a For Loop with you can load you contacts n times in the list. My list is about 900 items total. So if your device has 10 contacts, you should let the For Next statement do 90 loops.

On iPhone/iPad development in Objective-C I've used a lot of Apple's equivalent of your ULV, called UITableViews. UITableview uses 2 numbers to store the position. It has sections and rows, so you have a sectionnumber and a rownumber. The sectionnumber is what you would call 'separatorcount' above the position. The rownumber is the position relative to the position of the separator. These two numbers are together in a object called indexPath. So you have indexPath.section and indexPath.row

What I'm trying to say that your ULV is great, but to my opinion it can be greater if funtions like Item_ContentFiller and bmp_CustomLoad would have extra parameter indexPath with is an object that has the properties row, section and maybe even position that the developer can use. I would also prefer to rename the parameter itemID to just tag, since you told me the ULV doesn't use ItemID! By naming it tag it is clear to the developer he can put anything he wants in it, or nothing. If ULV would work like that, the developer can use both list an map without converting. It would make the ULV easier to use.

Just my 2 cents,
 

Informatix

Expert
Licensed User
Longtime User
I can't see why Position is not usable. It is the exact position of the item in the list. If you give another index to the list, the ULV will have to translate this index to the real position. And I checked: dividers are not counted, so they are not a problem. To avoid any confusion, I prefer to define the terms: a separator is created by the user, a divider is created by the ULV (to separate the items). The ULV cannot know that an item is a separator because it does not care of what the item contains and what kind of item it is. So it's a bit difficult to count anything after a section when you don't know what is a section and where it is.
If there's a strong demand, I can add functions to handle that in the next version. In this case, you will have to pass a map/array/list to the list to indicate what items are separators. And the list will have a function to translate the given position or ItemID to 1) the separator index and 2) the position starting from this separator (or the contrary). It's not a bad idea and can be really useful in some cases.

Tag and ID are different things. A Tag is just a placeholder to store any user data related to the object. It's the same concept in all languages where you can find this property. Of course, you can store the ID of the object in it, but you can also store an array of integers, a random number used by the object, a reference to its parent, etc. You cannot say what Tag contains before looking at this content, so using this name in a specific way would be confusing. On the contrary, ID is a name widely used in the computer science to design the unique identifier of an object and nothing else. And I never said that the ItemID is not used by ULV ! Or I was drunk. ;) I said it was not managed by ULV and it is mainly for your own usage because the list doesn't need it to work. My list doesn't create it, doesn't change it, doesn't delete it. It's a convenient way for you to design precisely an item when you don't know exactly where it is in the list (after a sort for example) and you're free to create it with the number format or the sequence that you wish. The ID has been added especially for you, the user, not for internal reasons. ULV could work without it; ULV could use only positions. But that doesn't mean it is not used. It is used when you transmit it as a parameter (or this parameter would be useless). And the ULV sent it back to you whenever possible.
 

Eduard

Active Member
Licensed User
Longtime User
position is usable but it is not available in bmp_CustomLoad and txt_CustomLoad, and it is increased by the separators. I need to add extra code to my project to get it.

In this case, you will have to pass a map/array/list to the list to indicate what items are separators

I think it should always be the otherway around. The ULV should call a sub that returns the needed object.

I think that a perfect Item_ContentFiller should look like this:

B4X:
Sub ULV_Item_ContentFiller(LayoutPanel As Panel, indexPath as ulvIndexPath) as ulvItem


'indexPath has following information for developer to get data
indexPath.section
indexPath.position
indexPath.relativePosition

Dim item as ulvTime
item.tag=ItemID
item.LayoutName="myLayoutname"


[...]

Return item
End Sub


and separators as follow
B4X:
Sub ULV_Separator_ContentFiller(LayoutPanel As Panel, section as Int) as ulvItem

Return item
End Sub

of course you'll need:

B4X:
Sub ULV_NumberOfItemsInSection(section as Int) as Int

Return 6543
End Sub

Sub ULV_NumberOfSections as Int

Return 3
End Sub


This is how Apple works more or less. I think it is worth thinking about if you could adopt something.
 

Informatix

Expert
Licensed User
Longtime User
position is usable but it is not available in bmp_CustomLoad and txt_CustomLoad, and it is increased by the separators. I need to add extra code to my project to get it.

You don't need the position at all in a custom loader because you load a resource. The Id is not the id of an item but the id of a resource (id of a picture, id of a text). That's why I called these IDs ImgID and TextID, and they are of a different type (string instead of long). You can have different items pointing to the same resource (e.g. having the same image). So ItemID <> ImgID. If each item has its own resource, like in your project where each contact has its own photo, then you can define ImgID = ItemID but it's a particular case.

When you say that the position is increased by separators, I agree that's a problem because that prevents from using an array (which do not include the separators), but you should not use positions in this case, only IDs. In most cases, it's better to use the IDs than the positions. As I already said, positions can change, IDs cannot.
Think about records in a database. How do you handle them? With an ID, not with the position. It's the same with ULV.

I think that a perfect Item_ContentFiller should look like this:
That supposes that ULV can identify that an item is a section/separator but you don't explain how to recognize this kind of item.

This is how Apple works more or less. I think it is worth thinking about if you could adopt something.

UITableView and ULV use different approaches, so a thing that is easy to do with ULV will be complicated with UITableView.
 
Last edited:

Eduard

Active Member
Licensed User
Longtime User
You don't need the position at all in a custom loader because you load a resource.
The image/text is just loaded and I want to add it to my own list. Therefore I need the position (index) where to store it. In my project I don't use the cache of the ULV, but my own list. And no: I don't use a map of items. A list works better. I can sort a List, remove Items without a gap en change the order. If the user changed the orientation of the device my list is still having data en the ULV lost everything. So I do want to use a list and not a map.

When you say that the position is increased by separators, I agree that's a problem because that prevents from using an array (which do not include the separators), but you should not use positions in this case, only IDs. In most cases, it's better to use the IDs than the positions. As I already said, positions can change, IDs cannot.
Think about records in a database. How do you handle them? With an ID, not with the position. It's the same with ULV.
In a database you often use rownr. In fact, Oracle has a special reserved column name for rownr. Every single query you can use rownr. It's there because there a numerous situations where this is handy. For example if you need item at previous position.

In my project I need for example the item at the next position when I display an separator. Again, a list is handy, and a map not.


That supposes that ULV can identify that an item is a section/separator but you don't explain how to recognize this kind of item.
There a many solutions for that. The apple way is to call a sub for the number of sections and for each section a sub for the number of rows in this section.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
I was thinking to your proposal and here's how I could implement something in the next version:
Items could be grouped in an entity called Group with a function AddItemtoGroup, which would do an AddItem and attach the item to a group. You could move/delete a group with one function and all items in the group would be moved/deleted. The group could have an header and a footer, and of course you could use a position relative to the group first item. The group would have properties like an ID to ease its access and indexing, and could be used as a child or as a parent in an expanded tree.
 

Eduard

Active Member
Licensed User
Longtime User
Sounds good. But it's not lack of functionality that I'm worried about. Your ULV is great. It's easy of programming. You should provide all available information in the subs that ULV calls back. I need position available in bmp_CustomLoad. An easy way of getting it from your class with for exmple the method positionOneOfItemID as Int / positionsOfItemID as Array would be welcome.

Take other suggestions into account as well: I'd really benefit from http POST, and my suggestion to use more objects (ulvItem, indexPath) and callbacks. Use of Objects and Callbacks makes it much easier for you to add functionality in the future.

ULV requires me to add all items with addItem in advance, but in my opinion ULV doesn't even need to know the itemID's of Items that are not visible. See my post at #146. ULV only needs to know how many items there are in advance and could call a sub to get the item. That is faster.
 

Informatix

Expert
Licensed User
Longtime User
The resource is just loaded and I want to add it to my own list. Therefore I need the position (index) where to store it. In my project I don't use the cache of the ULV, but my own list.

As I explained to you in my email, don't use your own list. Compared to the cache, that's not thread-safe and not efficient.

And no: I don't use a map of items. A list works better. I can sort a List, remove Items without a gap en change the order. If the user changed the orientation of the device my list is still having data en the ULV lost everything. So I do want to use a list and not a map.

I don't understand "the ULV lost everything". 1) The ULV does not contain data and 2) The ULV has a persistent cache on disk, so it can reload your resources very quickly, while your list is cleared when you exit the application and has to be refilled (which is a real problem when the images are on a distant server and your connection is limited).

In a database you often use rownr. In fact, Oracle has a special reserved column name for rownr. Every single query you can use rownr.

Of course, there are situations where you use the position of a row. Nothing is only black or white. But when you update a table, your Where clause looks usually like : "WHERE ID=152" or "WHERE Country='France'", almost never "WHERE rowid = 15" (a strong reason in the professional world to not do that is because you're not alone to use this table in the database and someone may have inserted or deleted rows while you're updating your record).

In my project I need for example the item at the next position when I display an separator. Again, a list is handy, and a map not.

You have to understand that I cannot transform my list for a very particular use. You prefer to use lists and positions, another guy will prefer to use maps and IDs, ok: my goal is to allow both of you to use the ULV, not to ease the job for the first and prevent the second from using his map. Intermediate solutions are not the best for a particular need, I agree.

There a many solutions for that. The apple way is to call a sub for the number of sections and for each section a sub for the number of rows in this section.

That doesn't answer to my question. How do I know the number of sections? where they are? how the ULV can distinguish these separators from other items?
 

Informatix

Expert
Licensed User
Longtime User
I need position available in bmp_CustomLoad.
Don't store your resources in anything else than the cache. This cache is more efficient than anything you could implement in B4A, is thread-safe, and does not need at all the position because resources and items are different things, with a relation 0-n between them (i.e. an item may need 0 to n resources). Since there's no need to sort resources or to have them in sequence, your own list brings no added value. Resources are not retrieved by a position (this position means nothing) so I can't see why I should add the item position in the parameters of the custom loader. If you want to pass a position to your loader, you can use the ImgID/TextID field. Its content is up to you. You can also put what you want after "custom:".

I feel like I'm repeating always the same things so maybe it's time to end this discussion.

ULV requires me to add all items with addItem in advance, but in my opinion ULV doesn't even need to know the itemID's of Items that are not visible. See my post at #146. ULV only needs to know how many items there are in advance and could call a sub to get the item. That is faster.
I think that you did not understand how the ULV is built. And I'm too short on time today to re-explain.
 

Eduard

Active Member
Licensed User
Longtime User
I didn't know that B4A is not thread safe. That explains a lot. I guess I must use the ULV cache functions.

It true that don't know how the ULV is built. I only read the documentation. I hope you'll add the fact that B4A is not thread safe in your documentation. Your e-mail made it al clear: - never create/update an external map/array/list in this sub;

Thanks
 

Informatix

Expert
Licensed User
Longtime User
I didn't know that B4A is not thread safe. That explains a lot. I guess I must use the ULV cache functions.

It true that don't know how the ULV is built. I only read the documentation. I hope you'll add the fact that B4A is not thread safe in your documentation. Your e-mail made it al clear: - never create/update an external map/array/list in this sub;

B4A cannot be thread-safe, only your program can. (For those who want to know what "thread-safe" means: Wikipedia article) B4A is missing the keywords and functions needed to allow you to make your code thread-safe (and to run it asynchronously), but there's a library filling the gap ("Threading"). Since it is easier, more efficient and more robust to use the cache features of ULV, I won't ever try to explain how to produce your own thread-safe code (which is not always easy in some cases).

What I wanted to mean when I said that you did not understand how ULV is built is that your following sentence is wrong and a bit strange: "ULV only needs to know how many items there are in advance and could call a sub to get the item. That is faster." In fact, ULV needs to know three things in advance: the number of items, their height and their ItemID. If I don't know the height of my list I cannot build a correct scrollbar on the right, and if I don't know the size of an item I have to request it in realtime then move all items to make enough room, which is very slow (remember that ULV can have different items of different heights and that requires a management of sizes completely absent from lists with even-sized items). It's faster to prepare my list with the correct sizes than any other solution and it's mandatory with a fast scroller (try to add a fast scroller to the TableView class and see the horrible performance that you get, despite all row heights are the same). I already explained why I need the ItemID since the beginning (to maintain the association with the position because positions may change).
What's strange in your sentence is "could call a sub to get the item". It's exactly what the ULV does.

I hope things are more clear now.
 

Marcos Alves

Well-Known Member
Licensed User
Longtime User
Can you stack items from te TOP? I have an application in which I need to load only some rows and if user scrools to te top I´m loading the next ones (at the TOP). If yes, I´ll donate. By the way, how is the price - did you stablished a minimum value or it´s depending on each one valuation?
 

Eduard

Active Member
Licensed User
Longtime User
Thanks Informatrix

Your ultimate listview is excellent and now I understand how it works, it's even better.:)

Have a question about cache and how unique an ID needs to be: Can I use the same number for both imgID and textID? And if I use more than one ULV in my app: do I need to make imgID's unique across the program? The reason I ask is that I saw that the cache is preserved and used even if I recompile the program. I think this is a great feature, but then I assume ID's need to be truly unique across the program, right?

Update: I did a test and saw that two ULV's will use the same cache unless specified a different cache folder. That can be usefull, but mostly I don't want that so I'll be using a different cache folder for each activity.

B4X:
    Dim cacheFolder As String = Me
    cacheFolder = File.DirInternalCache & "/" & cacheFolder.SubString(cacheFolder.LastIndexOf(".")+1)
    myULV.Initialize(cachePercentage,diskCacheMb,cacheFolder,"myULV")

Could you please add this to the documentation too? (That all ULV's use the same cache unless a different cache folder is specified)
 
Last edited:

Eduard

Active Member
Licensed User
Longtime User
I did another test: textID and bitmapID cannot be the same. ID's need to be truly unique in ULV.

Please add this to documentation too (a textID must not be the same as bitmapID)
 

Eduard

Active Member
Licensed User
Longtime User
Some more questions:

When (what situation) should I use
1) CloseCache and ReopenDiskCache?
2) StopAsyncLoading?
3) ClearMemoryCache
 
Top