B4A Library [B4X] PreoptimizedCLV - Lazy loading extension for xCustomListView

This is a cross platform class that extends xCustomListView and makes it "lazier".
Lazy loading is explained here: https://www.b4x.com/android/forum/t...ew-lazy-loading-virtualization.87930/#content and here: https://www.b4x.com/android/forum/threads/💡-part-1-basics-creating-long-lists-using-xcustomlistview-with-lazy-loading-newer-developers.114096/#content
This is a performance optimization. Instead of creating all items with all the views when the app starts, we create the views as the items become visible.

There are all kinds of lazy loading methods.
An incomplete list:
  • Level 0: No lazy loading at all. Most applications should choose this one. It is simplest to implement and the easiest one to customize. This method is good for up to several hundred items, depending of course on the items content.
  • Level 1: Creating all layout except of the "heavy parts", usually bitmaps.
  • Level 2: Only creating layouts for the visible items.
There are several variants for level 2, you can load a new layout each time and discard old layouts or you can cache the layouts and reuse them (example: https://www.b4x.com/android/forum/t...-imageviews-and-many-rows.101431/#post-636920).

When you add items to xCLV, each item is made of two panels. The first panel is an internal panel and the second one is the panel passed in the CLV.Add call.
This is true even with lazy loading. If you are creating lists with thousands of items or more than these panels become an issue. PreoptimizedCLV solves this issue.
PreoptimizedCLV implements the level 2 method without the need to create the two panels. The internal panel is reused and the second panel is created for visible items only.

Edit: When I first developed this library I was a bit skeptic about its usefulness, as users will never scroll more than a few hundred items at most. However Klaus suggested to add fast scrolling feature and it was a great idea! With fast scrolling this library becomes useful.

It looks like this:


Full example:
B4X:
Sub Globals
    Private CustomListView1 As CustomListView
    Private PCLV As PreoptimizedCLV
    Private xui As XUI
    Private Label1 As B4XView
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("1")
    PCLV.Initialize(Me, "PCLV", CustomListView1)
    Dim words As List = File.ReadList(File.DirAssets, "english.txt")
    For Each word As String In words
        PCLV.AddItem(100dip, xui.Color_White, word)
    Next
    PCLV.Commit
End Sub

Sub PCLV_HintRequested (Index As Int) As Object
    Dim word As String = CustomListView1.GetValue(Index)
    Return word
End Sub

Sub CustomListView1_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)
    For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
        Dim item As CLVItem = CustomListView1.GetRawListItem(i)
        Dim pnl As B4XView = xui.CreatePanel("")
        item.Panel.AddView(pnl, 0, 0, item.Panel.Width, item.Panel.Height)
        'Create the item layout
        pnl.LoadLayout("Item")
        Label1.Text = item.Value
    Next
End Sub

Step #1:
Add the items with PCLV.AddItem. You need to pass the item size (height for vertical lists and width for horizontal lists), background color and value. Value can be any object you like including complex custom types that hold all the data needed to later build the layout.

Step #2:
Call PCLV.Commit.

Step #3:
Implement the VisibleRangeChanged event. The code should look like:
B4X:
For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
  Dim item As CLVItem = CustomListView1.GetRawListItem(i)
  'Create the layout for item i and add it to item.Panel.
Next

Step #4:
Implement the HintRequested event and return the string or CSBuilder that will be displayed when the user scrolls the list.

That's it.

Once the items are committed you can add or remove items by modifying CLV directly and calling PCLV.
B4X:
CustomListView1.InsertAtTextItem(Index, "New Item", "")
PCLV.ListChangedExternally
PCLV ignores these items.

Notes

  • The item layout should have a transparent background.
  • Better to remove AutoScaleAll and set the animation duration to 0 in the item layout.
  • The list should scroll very smoothly in release mode.
  • Make sure that "Show Scrollbar" option is unchecked in CLV custom properties.
  • PCLV.pnlOvarlay is the panel that hides the list when it fast scrolls. PCLV.lblHint is the label that appears. Both can be customized.
  • You can change the seek bar interval by setting PCLV.NumberOfSteps, before calling PCLV.Commit. The default value is 20. Don't set it to be too high as it will be difficult for the user to select a specific point.
  • ScrollView height in Android is limited to 16,777,215 pixels. The exact limit depends on the device scale and the items height. As a rule of thumb you can show up to 50,000 100dip tall items. Such lists are not practical anyway: https://www.b4x.com/android/forum/t...ension-for-xcustomlistview.115289/post-721157

Example #1 shows the 10,000 most frequent English words. Source: https://github.com/first20hours/google-10000-english
Example #2 is a port of the "Corona cases" cross platform example based on B4XTable + xChart. It depends on xChart library.
Data source: https://ourworldindata.org/coronavirus-source-data

The b4xlib is a cross platform library.
 

Attachments

  • PreoptimizedCLV.b4xlib
    6.3 KB · Views: 873
  • PCLV_Example1.zip
    40.1 KB · Views: 930
  • PCLV_Example2.zip
    43.8 KB · Views: 853
Last edited:

monki

Active Member
Licensed User
hello erel,
the same error message as with Klaus. I inserted 80000 entries for the test, but the list cannot be scrolled to the end, if you pull the fast scroller down, the entries are shown to the end, but when you release the scroller, the list cannot be scrolled to the end Problem occurs from about 90000 entries.

monki
 

monki

Active Member
Licensed User
Hi there,
I only changed the creation of the entries from the sample code as follows for the test




B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("1")
    PCLV.Initialize(Me, "PCLV", CustomListView1)
    Dim words As List = File.ReadList(File.DirAssets, "english.txt")

'    For Each word As String In words
    For t= 1 To 90000
   
'  
        PCLV.AddItem(100dip, xui.Color_White, t)

    Next
    PCLV.Commit
End Sub
 

Jorge M A

Well-Known Member
Licensed User
changed the creation of the entries
I think it is not enough. The text file has only 10000 entries.
In my case, i generate other txt file with 230,000 (stressing the list), but have the same problem.
 
Last edited:

Erel

Administrator
Staff member
Licensed User
I've added a note about the limit. See the first post.

There are UX limits that make lists with 50k+ items impractical anyway. I'll explain:
The fast scroller cannot have too many stop points or it will be very difficult to accurately get to the desired point.
By default the fast scroller is set to have 20 steps (stop points). With 50k items the number of items between each step will be about 2,500 items. No user will ever want to scroll this number of items to get to a specific item.
 
Last edited:

Jorge M A

Well-Known Member
Licensed User
Just for the record and since PreoptimizedCLV is a cross platform, I transferred the sample code to B4J, and it worked without any problem with 230,000 items.
Many Thanks!
 

seyed_27

Member
error in PCLV_Example2.zip
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
Read countries data
** Activity (main) Resume **
xchart_designercreateview (java line: 1608)
java.lang.RuntimeException: Cannot parse: null as boolean
at anywheresoftware.b4a.BA.parseBoolean(BA.java:612)
at anywheresoftware.b4a.BA.ObjectToBoolean(BA.java:682)
at b4a.example.xchart._designercreateview(xchart.java:1608)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:67)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:162)
at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:134)
at anywheresoftware.b4a.objects.B4XViewWrapper.LoadLayout(B4XViewWrapper.java:293)
at b4a.example.main._addchart(main.java:444)
at b4a.example.main._clv_visiblerangechanged(main.java:511)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
at anywheresoftware.b4a.keywords.Common$11.run(Common.java:1179)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6680)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
 

klaus

Expert
Licensed User
Erel, I would suggest you to set these two properties in the Designer for the chart in your project.

1585904813456.png

1585904828651.png


Then we would see two scales instead of one.

1585904589835.png
1585904566364.png
 

MrKim

Well-Known Member
Licensed User
Erel, I love it! I was going to give up on BCTextEngine as just too slow for my needs but this solved it but with a couple of problems.

First, the (Fast?)scrolling scroll bar doesn't work. It is odd dragging it doesn't work at all but I can scroll with the mouse and when I do the scrollbar "Dot" "Tracks" it perfectly. For my needs I would prefer to have the option of using the regular scrollbar My list isn't that large (0-250) it just gets updated frequently. I have two other customlistviews on the form and I want them to all look the same. Allos, the scrollbar (Line Really) sticks out into the list.
1586087270182.png


Sigh, also Using the BBLabel I had finally figured out how to store and retrieve my map file in Both the on click event and when dragging and dropping. After switching the code to this I can't get it to work again.

Here is my code snippets:
B4X:
        Do While Crsr.NextRow = True
                Dim M As Map
                M.Initialize
                M.Put("Os_JobNum", Crsr.GetString("Os_JobNum"))
                M.Put("Os_ReleaseNum", Crsr.GetString("Os_ReleaseNum"))
                M.Put("Os_SeqNum", Crsr.GetString("Os_SeqNum"))
                M.Put("Os_WCCode", Crsr.GetString("Os_WCCode"))
                M.Put("Os_ID", Crsr.GetInt("Os_ID"))
                M.Put("Scheduled", False)
                PCLV.AddItem(50, xui.Color_ARGB(255, 186, 204, 210), M)
                zx=zx+1
                If zx = 12 Then
                Sleep(0)  'forces view of what is loaded so far
            End If
            'Sleep(1)
        Loop
        PCLV.Commit
        Crsr.Close


Sub PCLV_HintRequested (Index As Int) As Object
    Dim word As Map = UnSchedOpsList.GetValue(Index)
    Return word
End Sub


Sub UnSchedOpsList_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)
    'Log("Now")
    For Each i As Int In PCLV.VisibleRangeChanged(FirstIndex, LastIndex)
        Dim item As CLVItem = UnSchedOpsList.GetRawListItem(i)
        'Create the layout for item i and add it to item.Panel.
        Dim M As Map = item.Value
        Dim P As B4XView = xui.CreatePanel("UnschOPs")', TE As BCTextEngine
        Dim DT As String = M.Get("Os_StartbyDate")
        If DADMod.Isnull(DT) Then DT = "N/A" Else DT = DT.SubString2(0, 5)
        P.Tag = M
        P.LoadLayout("BBLabel")
        M.Put("BBLbl", P.GetView(0).Tag)
        TextEngine.Initialize(P)
        UnschOPsBB.Text = ScheduleMod.CheckForLate(M.Get("Os_StartbyDate"), 3) & $"  [Color=blue][b][u]"$ & M.Get("Os_JobNum") & $"[/u][/b][/Color] / [Color=black][b][u]"$ & M.Get("Os_ReleaseNum") & $"[/u][/b][/Color] Op [Color=#0000ff][b][u]"$ & M.Get("Os_SeqNum") & $"[/u][/b][/Color]"$
        'item.Color = xui.Color_ARGB(255, 126, 180, 250)
        item.Panel.AddView(P, 0, 0, 300, 50)
        DandD1.MakeDragSource(item.Panel, "DandD1")
        UnschOPsBB.Tag = M.Get("Os_ID")
    Next
End Sub
For one thing, since we are no longer using CLV.Add but PCLV.AddItem I am no longer sure exactly what is returned inSub CLV_ItemClick. I used to store the Map in My Panel.Tag (P) and and Make P the Value returned. That no longer works. I have now made item.Panel my drag source, which does start the drag event, but again, I cannotfigure out how to retrieve my map from the tag.

Thanks for your help.
 
Top