Android Tutorial 💡 Part 2 Examples - Creating long lists using xCustomListView with Lazy Loading - Newer developers

Hello All,
If you're not experienced with using xCustomListView (xCLV) with Lazy Loading (LL) when it comes to loading long lists of data, then I strongly suggest that you read the following post first >>>💡 Part 1 Basics <<< as this post is all about getting you introduced to the very basics of xCLV with LL.

By now most B4X developers should be aware that you can create long lists or card layouts lists by using >>> XcustomListView <<<, but what many of you are not aware of is the fact that you do not need to pre-load your xCLV (xCustomListView) with all the data at once, doing so can and does in many cases slows down the performance of your applications to an extremely slow crawl. In the link above, Erel not only shows B4X developers how to implement xCLV correctly, but has also added a few link to other associated posts that developers may also find useful, one such useful link is about LL (Lazy loading). Even though the links are there, I personally believe that some developers are not reading the entire post by Erel and are making the fatal mistake of skipping past the images straight to download the example code. Erel has in fact put all the source code necessary to accomplish xCLV with LL in two separate posts, but either too many B4X developers are skipping the links to Erel multiple posts, or developers have absolutely no idea what the term Lazy Loading actually means.

What is Lazy Loading?
Lazy loading is nothing new and has been around for many years in computer programs and also on the web, for example Google, eBay and Facebook used LL for displaying long lists of images and general information. It's a technique used to only load information into a list or card as and when you are getting closer to viewing that particular information. How exactly does LL work. Let's say you can only see 10 items in a list on your mobile device screen that actually contains 15 items. As you start scrolling down the list more items will be added to the bottom of that list, the list will be populated like this. 15 items, 30 items, 45 items, 60 items, 75 items etc etc etc until it reached the end of the list. Please note, I'm using 15 as an example but you can set the list population to any amount that you want to within reason. If you are getting your results from a database, I highly recommend that you limit your sql query size to 2000 results, or better still to 1000 results by using the sql LIMIT clause. This will lead to greater performance of your mobile or desktop applications over time as users add more information into databases. Using the LIMIT clause is highly recommended for better performance but not essential.

I have attached 4 fully working xCLV with LL examples (1 in each of the first 4 posts in this thread) for new and existing B4X developers to hopefully learn from.

Here is the Lazy Loading example displaying invoices in a list. The B4X logos are randomly selected from the assets folder as each item is being added to the list. There are only 412 invoices in this particular example, but the xCLV can run extremely smoothly with thousands in invoices populating the list.

>>> CLICK HERE <<< to download the Invoices example code.

Invoices.jpg


Thanks again to Erel for creating the B4X suite of development tools. Creating Android, iOS, Desktop and Microcontroller solutions have never been easier to accomplish as B4X is basically the same language throughout all 4 RAD IDE's...
 
Last edited:

kps

Member
@Peter Simpson
Thank you for the example under #1.
I have always felt comfortable and very educative to take an existing example, modify it, play with it and understand how it works.

Out of curiosity, I wanted to check if the search feature works for non-English languages also.
I modified the names in chinook.db and tried it out.
It works!
I am so excited !

Thanks a lot.

Best regards,
KPS
 

Daniel44

Active Member
Licensed User
Here is the Lazy Loading example for loading airport information from the SQLite database that has 7698 rows of airport data.

In my example video from last week, I was loading all 7698 rows of data directly into the xCLV and it populating extremely quickly. I strongly suggest that you DO NOT replicate what I did and load all the data at once. I have changed the sql query to limit the results to 2000 rows of data. I have a few fast devices that allowed me to accomplish populating all the data relatively quickly, slower devices would definitely struggled to accomplish the same feat.

>>> CLICK HERE <<< to download the Airports example code.

View attachment 88728

Enjoy...

Hey Sir wonderful job it is very useful. Can you answer a question? I have a database with Countries and their cities and I'm using your Airport Example but I don't know how group the countries with their cities, in the airport example the header shows the airport name and below its city. I'm looking for something like than but below the header all Country's cities Can you post an example ? Thank you Sir
 

Peter Simpson

Expert
Licensed User
Hey Sir wonderful job it is very useful. Can you answer a question? I have a database with Countries and their cities and I'm using your Airport Example but I don't know how group the countries with their cities, in the airport example the header shows the airport name and below its city. I'm looking for something like than but below the header all Country's cities Can you post an example ? Thank you Sir

Hello @Daniel44,
Working from memory (or should I say what I think is a logical design), I think that the following should work. I've not tested it, I'm answering your question directly on my phone, I have not computers or laptops currently switched on.
B4X:
SELECT * FROM airports ORDER BY Country, City ASC;
Or something like that.

Enjoy...
 

Daniel44

Active Member
Licensed User
Hello @Daniel44,
Working from memory (or should I say what I think is a logical design), I think that the following should work. I've not tested it, I'm answering your question directly on my phone, I have not computers or laptops currently switched on.
B4X:
SELECT * FROM airports ORDER BY Country, City ASC;
Or something like that.

Enjoy...
Thank you for asking Sir. Maybe I didn't explain myself well. I've changed this:

B4X:
If New.Length = 0  Then
        AirportsCount = Starter.SQL.ExecQuerySingleResult("SELECT COUNT(*) FROM `airports`;") 'The amuont of airports listed in the database
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` ORDER BY `Name` ASC LIMIT ${Limit};"$, Null) 'I NO NOT RECOMMEND that you load the whole database
    Else
        AirportsCount = 0
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` WHERE `Name` Like '%${New}%' ORDER BY `Name` ASC LIMIT 500;"$, Null) 'Limited for slower devices
    End If

    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, rs As ResultSet)
    If Success Then
        Dim StartTime As Long = DateTime.Now
        Do While rs.NextRow
            Dim AD As AirportData
                AD.Initialize
                AD.Name    = rs.GetString("Name")
                AD.AirportID = rs.GetString("Airport ID")
                AD.IATA    = rs.GetString("IATA")
                AD.ICAO    = rs.GetString("ICAO")
                AD.City    = rs.GetString("City")
                AD.Latitude    = rs.GetString("Latitude")
                AD.Longitude = rs.GetString("Longitude")
                AD.Altitude    = $"${rs.GetString("Altitude in feet")} ft"$
                
            Dim Pnl As B4XView = XUI.CreatePanel("")
            Pnl.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 130dip) 'Panel height + 4 for drop shadow
            CLVAirports.Add(Pnl, AD)
        Loop
        rs.Close



To this:
B4X:
Dim SenderFilter As Object
    If New.Length = 0  Then
        AirportsCount = Starter.SQL.ExecQuerySingleResult("SELECT COUNT(*) FROM `airports`;") 'The amuont of airports listed in the database
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT  * FROM airports order by `Country`,`City` ASC LIMIT ${Limit};"$, Null) 'I NO NOT RECOMMEND that you load the whole database
    Else
        AirportsCount = 0
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` WHERE `Country` Like '%${New}%' ORDER BY `Country` ASC LIMIT 500;"$, Null) 'Limited for slower devices
    End If

    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, rs As ResultSet)
    If Success Then
        Dim StartTime As Long = DateTime.Now
        Do While rs.NextRow
            Dim AD As AirportData
                AD.Initialize
                AD.Name    = rs.GetString("Country")
                AD.AirportID = rs.GetString("Airport ID")
                AD.IATA    = rs.GetString("IATA")
                AD.ICAO    = rs.GetString("ICAO")
                AD.City    = rs.GetString("City")
                AD.Latitude    = rs.GetString("Latitude")
                AD.Longitude = rs.GetString("Longitude")
                AD.Altitude    = $"${rs.GetString("Altitude in feet")} ft"$
                
            Dim Pnl As B4XView = XUI.CreatePanel("")
            Pnl.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 130dip) 'Panel height + 4 for drop shadow
            CLVAirports.Add(Pnl, AD)
        Loop
        rs.Close

these changes show me this:
Screenshot_20200927-185958.png


and I would like this:

WISH.png


I mean i.e. each country with all its respective cities without the country repeating itself

Would that be possible? If so, what should I change?. Thank you Sir.
 

Unobtainius

Active Member
Licensed User
@Peter Simpson
ive just finished watching the b4a to b4j video and am simply mind blown. I know we are aiming to write b4x cross platform stuff and in a way your video demonstrates just why we can. I couldn't believe how easily you did it. Bravo sir and I especially loved the what the ?? bit. I'm in a hospital waiting room just now (for my old mum) so will have to wait to have a look at the lazy loading examples but I'm sure they will provide me lots of insight. Thanks Peter, keep the exceptional work
 

QinLiwei

New Member
Hi, Peter Simpson, I can't download these examples because I can't access www.dropbox.com, but I really need them. Could you upload them to the B4X forum or send me an email?My email address is :88251404@qq.com. I am looking forward to your reply. Thank you very much!
 

anOparator

Active Member
Licensed User
This is another good post on CustomListView but I get this error compiling the Invoices example, in Debug and Release mode using B4A 10.2
B4X:
Copying updated assets files (6)
*** Service (starter) Create ***
java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/core/content/ContextCompat;
    at anywheresoftware.b4a.objects.RuntimePermissions.GetSafeDirDefaultExternal(RuntimePermissions.java:120)
    at com.simplysoftware.chinookinvoices.starter$ResumableSub_Service_Create.resume(starter.java:188)
    at com.simplysoftware.chinookinvoices.starter._service_create(starter.java:165)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:732)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:348)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:255)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:144)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:193)
    at com.simplysoftware.chinookinvoices.starter.onCreate(starter.java:56)
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:3342)
    at android.app.ActivityThread.-wrap4(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1680)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6518)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.core.content.ContextCompat" on path: DexPathList[[zip file "/data/app/com.simplysoftware.chinookinvoices-n5cNwPjejJXmRQHC4CW1GA==/base.apk"],nativeLibraryDirectories=[/data/app/com.simplysoftware.chinookinvoices-n5cNwPjejJXmRQHC4CW1GA==/lib/arm, /system/lib, /vendor/lib]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:125)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
    ... 20 more
java.lang.RuntimeException: Unable to create service com.simplysoftware.chinookinvoices.starter: java.lang.RuntimeException: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/core/content/ContextCompat;
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:3352)
    at android.app.ActivityThread.-wrap4(Unknown Source:0)
It was uploaded to https://www.dropbox.com/s/6c669byshlonjrn/xCLV LL Invoices.zip?dl=0
What to do, what to do?
 

DonManfred

Expert
Licensed User
This is another good post on CustomListView but I get this error compiling the Invoices example, in Debug and Release mode using B4A 10.2
1. You should create a new thread for any issue you have
2. You are not using AndroidX. You still use android-support which is deprecated and not longer available.
Follow the Installation instructions carefully: https://www.b4x.com/b4a.html
 

Unobtainius

Active Member
Licensed User
Thank you for asking Sir. Maybe I didn't explain myself well. I've changed this:

B4X:
If New.Length = 0  Then
        AirportsCount = Starter.SQL.ExecQuerySingleResult("SELECT COUNT(*) FROM `airports`;") 'The amuont of airports listed in the database
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` ORDER BY `Name` ASC LIMIT ${Limit};"$, Null) 'I NO NOT RECOMMEND that you load the whole database
    Else
        AirportsCount = 0
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` WHERE `Name` Like '%${New}%' ORDER BY `Name` ASC LIMIT 500;"$, Null) 'Limited for slower devices
    End If

    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, rs As ResultSet)
    If Success Then
        Dim StartTime As Long = DateTime.Now
        Do While rs.NextRow
            Dim AD As AirportData
                AD.Initialize
                AD.Name    = rs.GetString("Name")
                AD.AirportID = rs.GetString("Airport ID")
                AD.IATA    = rs.GetString("IATA")
                AD.ICAO    = rs.GetString("ICAO")
                AD.City    = rs.GetString("City")
                AD.Latitude    = rs.GetString("Latitude")
                AD.Longitude = rs.GetString("Longitude")
                AD.Altitude    = $"${rs.GetString("Altitude in feet")} ft"$
               
            Dim Pnl As B4XView = XUI.CreatePanel("")
            Pnl.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 130dip) 'Panel height + 4 for drop shadow
            CLVAirports.Add(Pnl, AD)
        Loop
        rs.Close



To this:
B4X:
Dim SenderFilter As Object
    If New.Length = 0  Then
        AirportsCount = Starter.SQL.ExecQuerySingleResult("SELECT COUNT(*) FROM `airports`;") 'The amuont of airports listed in the database
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT  * FROM airports order by `Country`,`City` ASC LIMIT ${Limit};"$, Null) 'I NO NOT RECOMMEND that you load the whole database
    Else
        AirportsCount = 0
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` WHERE `Country` Like '%${New}%' ORDER BY `Country` ASC LIMIT 500;"$, Null) 'Limited for slower devices
    End If

    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, rs As ResultSet)
    If Success Then
        Dim StartTime As Long = DateTime.Now
        Do While rs.NextRow
            Dim AD As AirportData
                AD.Initialize
                AD.Name    = rs.GetString("Country")
                AD.AirportID = rs.GetString("Airport ID")
                AD.IATA    = rs.GetString("IATA")
                AD.ICAO    = rs.GetString("ICAO")
                AD.City    = rs.GetString("City")
                AD.Latitude    = rs.GetString("Latitude")
                AD.Longitude = rs.GetString("Longitude")
                AD.Altitude    = $"${rs.GetString("Altitude in feet")} ft"$
               
            Dim Pnl As B4XView = XUI.CreatePanel("")
            Pnl.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 130dip) 'Panel height + 4 for drop shadow
            CLVAirports.Add(Pnl, AD)
        Loop
        rs.Close

these changes show me this:
View attachment 100635

and I would like this:

View attachment 100636

I mean i.e. each country with all its respective cities without the country repeating itself

Would that be possible? If so, what should I change?. Thank you Sir.
If you think about the custom list control simply as a list that can accept any layout you want to place into each item, it opens your mind to many possibilities. In general people use it to show the same type of item layout over and over, but you don't have to.

For what you are looking to achieve I would have a layout for the header (country) and a layout for the airport. Then have a variable to hold the country name and if the country you are about to insert is different from that, insert the header as a new item, then the airport as a new item. If the country has not changed then just add the airport as an item. Just remember to update your variable to the country being inserted after your decision.

Just one possible solution
 

Daniel44

Active Member
Licensed User
If you think about the custom list control simply as a list that can accept any layout you want to place into each item, it opens your mind to many possibilities. In general people use it to show the same type of item layout over and over, but you don't have to.

For what you are looking to achieve I would have a layout for the header (country) and a layout for the airport. Then have a variable to hold the country name and if the country you are about to insert is different from that, insert the header as a new item, then the airport as a new item. If the country has not changed then just add the airport as an item. Just remember to update your variable to the country being inserted after your decision.

Just one possible solution
Thank you @Unobtainius
 
Hi @Unobtainius and @Daniel44,

I have just made it a header for a group of airport(s) with same country.
I make it because it is very helpful if customlistview present items grouped in section.


The layout Airports.bal is splitted into airportinfo1.bal for header and airportinfo2.bal for item

1634007735324.png

airportinfo1.bal

1634007780448.png

airportinfo2.bal

VisibleRangeChanged event:
Sub CLVAirports_VisibleRangeChanged (FirstIndex As Int, LastIndex As Int)
    LogColor("FirstIndex: " & FirstIndex & CRLF & "LastIndex: " & LastIndex, Colors.Magenta)
    Dim ExtraSize As Int = 25 'List size
    For i = Max(0, FirstIndex - ExtraSize) To Min(LastIndex + ExtraSize, CLVAirports.Size - 1)
        Dim Pnl As B4XView = CLVAirports.GetPanel(i)
        If i > FirstIndex - ExtraSize And i < LastIndex + ExtraSize Then
            If Pnl.NumberOfViews = 0 Then 'Add each item/layout to the list/main layout               
                Select Pnl.Tag
                    Case "header"
                        Dim country_1 As String = CLVAirports.GetValue(i)
                        Pnl.LoadLayout("airportinfo1")
                        LblName.Text = country_1
                    Case "item"
                        Dim AD As AirportData = CLVAirports.GetValue(i)
                        Pnl.LoadLayout("airportinfo2")
                        LblAirportId.Text = AD.AirportID
                        LblIATA.Text = AD.IATA
                        LblICAO.Text = AD.ICAO
                        LblCity.Text = AD.City
                        LblLatitude.Text = AD.Latitude
                        LblLongitude.Text = AD.Longitude
                        LblAltitude.Text = AD.Altitude
                    Case Else
                        Continue
                End Select               
            End If
        Else 'Not visible
            If Pnl.NumberOfViews > 0 Then
                Pnl.RemoveAllViews 'Remove none visable item/layouts from the list/main layout
            End If
        End If
    Next
End Sub

CLVAirports Add Row:
Sub TxtSearchFilter_TextChanged (Old As String, New As String)
    If New.Length = 1 Or New.Length = 2 Then Return
    CLVAirports.Clear
    Sleep(0)

    Dim SenderFilter As Object
    If New.Length = 0 Then
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT  * FROM airports order by `Country`,`City` ASC LIMIT 500;"$, Null)
    Else
        SenderFilter = Starter.sql.ExecQueryAsync("SQL", $"SELECT * FROM `airports` WHERE `Country` Like '%${New}%' ORDER BY `Country` ASC LIMIT 500;"$, Null) 'Limited for slower devices
    End If

    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, RS As ResultSet)
    If Success Then
        Dim StartTime As Long = DateTime.Now
        Dim idx As Int
        Dim currCountry As String = ""
        Do While RS.NextRow
            idx = idx + 1           
            If currCountry <> RS.GetString("Country") Then
                currCountry = RS.GetString("Country")
                Dim PnlHeader As B4XView = XUI.CreatePanel("")
                PnlHeader.Tag = "header"
                'Dim Height As Int = 60dip
                PnlHeader.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 65dip) 'Panel height + 4 for drop shadow
                CLVAirports.Add(PnlHeader, idx & ". " & RS.GetString("Country"))
            End If
            Dim Ad As AirportData
            Ad.Initialize
            Ad.Name    = RS.GetString("Country")
            Ad.AirportID = RS.GetString("Airport ID")
            Ad.IATA    = RS.GetString("IATA")
            Ad.ICAO    = RS.GetString("ICAO")
            Ad.City    = RS.GetString("City")
            Ad.Latitude    = RS.GetString("Latitude")
            Ad.Longitude = RS.GetString("Longitude")
            Ad.Altitude    = $"${RS.GetString("Altitude in feet")} ft"$               
            Dim PnlItem As B4XView = XUI.CreatePanel("")
            PnlItem.Tag = "item"
            PnlItem.SetLayoutAnimated(0, 0, 0, CLVAirports.AsView.Width, 70dip) 'Panel height + 4 for drop shadow
            CLVAirports.Add(PnlItem, Ad)
        Loop
        RS.Close       
        Log($"List population time = ${NumberFormat2((DateTime.Now - StartTime) / 1000, 1, 2, 2, False)} seconds to populate ${CLVAirports.Size} airport names"$)
    Else
        Log(LastException)
    End If   
    'Add an icon to the activity text by using CSBuilder
    Activity.Title = CS.Initialize.Typeface(Typeface.FONTAWESOME).Append(Chr(0xF072)).Append($" ${CLVAirports.Size} Airports"$).PopAll
End Sub
 
Top