Android Question Can we make a clone of a customListview

Jeanc161

Member
Licensed User
Longtime User
Here is the problem..
I have a Main and a Service module + other class module in the same code block
Let say i have a panel containing a Customlistview object Called TOC that i populate with items. But the same customlistview is use for other layout such as a menu list, and a search result list. The problem is when i minimize the app, i loose this customListview and have to rebuild it from the ground up. The same is also true if i load a layout for the search result that appear in the same panel as the TOC Panel.

I also have defined Global Vars in the service that hold my TOC content and Menu content and the Search result content as objects, wich is keept when the app is minimized and i do not need to load these values again in a list using Custom Type structure.

So what i want to be accomplish is to Build the TOC content from the Main at startup, also the menu content at startup, wich can take some time to load into the customlistview item by item and save it to the service module so that it could be reused when needed.
So i'm wondering if it is possible to make a copy of this TOC customlistview or the MENU listview and saved it to a variable in the service module to be keept in memory so that i don't have to load it item by item again, but just swap the object from the service module to the main Customlistview object wich is store in a panel on the main display area.

I've been told that you can't store view object in global vars, but can we save the CustomlistviewObject as global object to the service module and been able to reload that object in the main module when we want to display the TOC object, without reloading item by item, but just as a global object that we can swap. The TOC must be loaded once and the same as the menu, the search result object is build when we make a search, but that panel can be sidebar using the B4XDrawer that we swap into view with a right to left or left to right gesture.

Anybody have any idea on how to do this, that will save me a lot of time rebuilding each Listview when the application resume. ???
 

emexes

Expert
Licensed User
I don't have time to test this right now, but: a slightly different tack might be to prevent the CustomListView object from being deleted (garbage collected).

If you have a reference to the object from another process, that might be enough to do it.

Eg, in Starter module, have Public SirLockalot As Object = Null, and then in the Create Activity Sub something like:
B4X:
If Starter.SirLockalot <> Null then
    TOCCLV = Starter.SirLockalot    'reuse previous ("saved") instance
    'might need to update view's parent to be the newly-created Activity rather than the old (deleted) Activity
Else
    TOCCLV.Initialize    'or has LoadLayout already done this for you?
    'build up TOCCLV
    Starter.SirLockalot = TOCCLV    'could probably do this earlier, but... feels safer to wait until TOCCLV resembles something valid
End If
This probably breaks a few rules or customs, might not even work anyway. I will happily correct or delete this post based on whether it actually works or not (rather than leaving a dud signpost to dead-end trail hanging about the forum).
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
I will try to save the object in the starter module from main and see if it does save and retreive the Listview on app_resume will keep you posted if it works.
Thanks...
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
Well try it in many differents ways, it save the object allright, but there is no way to restore it without reinitializing the customlistview, it simply did not swap from the service module to the main activity. I will try to find another alternative for this to work.

In the meanwhile if somebody else have a trick to accomplish that, i certainly will like to hear it.

Note that the customlistview is build using the B4XDrawer object and the Listview is on the leftPanel, as well as the other (Menu, Toc, Search) everything work perfectly except that i will like to save each section to a temp var in the service module to be restored when calling back the display of each section, but so far no luck.

And if i try lo save it to a file as an object it takes about the same amount of time to load back the object then rebuilding the listview item by item.

So i'm open to suggestions.
 
Upvote 0

emexes

Expert
Licensed User
Well try it in many differents ways
I gave it a red-hot go as well, likewise no success. Also tried stuff like keeping a separate list of the panels, and adding them to a CLV if the CLV has fewer elements than the list. Well, it was worth a shot.

One thing that became apparent is that the CLV in my test app normally retains its contents between invocations of the Activity. I assume yours does not because you have far more data or larger activities or something, that is triggering clearing out of paused Activities.
So i'm open to suggestions.
I too am interested to know if there is a way of doing it.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
1. The layout is not affected by the app being minimized (home screen and back to the app).
2. The layout is destroyed when the activity is destroyed.
3. You shouldn't try to keep views between different activities instances.
4. The main problem is that it takes too long to create the lists. This means that you need to switch to lazy loading. It is quite simple to do and everything will happen very quickly.
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
Well the thing is i don't load any other activity. I use the B4XDrawer class, and load my main page on the centerPanel wich contains a webview to display pages that are stored on a list object, that i load at startup using RandomAccessFile. I do this so it can be compatible with B4I and so far it is all compatible.

But using the B4XDrawer class only allow me a single swapping panel mleftPanel and i use that panel to load a TOC list, a menu list and a search result list with custom layout. So when i load the TOC list the first time, it display it until the user select from the top line menu to load a menu or start a new search of the text in a list that is loaded into memory at startup. This loading of the list at startup is fast and ok.

So what i need to do is save the leftpanel layout for the TOC that has already been loaded at startup, save it somewhere into memory var so i don't have to reload it item by item again, and load the other option on the leftPanel of the Drawer object, so when i do that i loose the TOC panel and have to reload the TOC again when needed,it is also the same for the search panel if i load it i loose the TOC (The TOC is the longest to load) and can be anoying when switching form panel to panel. So so far i did not find a method that can save that panel to memory var that will be saved in the service module, to be loaded when the activity resume.

The TOC list panel is my worst waiting time. Right now i have a list of maybe 1500 to 1900 item to load, but i'm working on another TOC that will contains 6500 to 7000 items to load, and that take about 6 to 10 secs to load depending of the mobile device. I tried to saved it to a cache, but the problem is restoting that panel view, not to save it it does that very quickly. So i'm out of idea, My code is optimized to it's max to save as much miliseconds as possible but still.

So if more idea out there i will appreciate, and need be i can show you some of my code that i use. The code is based on the B4XDrawer object for some who nows this class and i need to be compatible between B4A and B4i, so no specialized library can be used unless compatible between those 2 platforms

Thanks All...
 
Upvote 0

emexes

Expert
Licensed User
i'm working on another TOC that will contains 6500 to 7000 items to load
Your app users are going to be spending a lot of time scrolling through that list. Can you organize the TOC into two or three levels, which would reduce the sizes down to more manageable 30-to-100 items each?

Alternately, a pseudo-searchable CLV that is only loaded up with the first 100 matches to text typed into an EditText? (he says, going off to investigate the practicality of this...)

Edit: I tried loading a CLV with 8000 text items, got roughly the same times as you mentioned.
 
Upvote 0

emexes

Expert
Licensed User
This mostly works, except that it probably needs something to restart the search if another search starts before the current search finishes.
B4X:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

    Private EditText1 As EditText
    Dim CustomListView1 As CustomListView
  
    Dim L As List
    Dim A() As String   'duplicate of L for quicker searching: already ToUpperCase'd, and no need to cast to string
  
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    Activity.LoadLayout("Layout1")
  
    CreateSampleList(2000)
  
    UpdateArrayFromList
    LoadFirst100
  
End Sub

Sub CreateSampleList(N As Int)
  
    L.Initialize
    Dim StartTime As Long = DateTime.now
    For I = 1 To N
        Dim S As String = Chr(Rnd(65, 65+26)) & Chr(Rnd(65, 65+26)) & Chr(Rnd(65, 65+26)) & " " & I
        L.Add(S)
    Next
    Dim EndTime As Long = DateTime.Now
    Log(EndTime - StartTime)
  
End Sub

Sub UpdateArrayFromList

    Dim A(L.Size) As String
    Dim S As String
    For I = 0 To L.Size - 1
        S = L.Get(I)
        A(I) = S.ToUpperCase
    Next
  
End Sub

Sub LoadFirst100
  
    Dim SearchString As String = EditText1.Text.ToUpperCase
  
    CustomListView1.Clear
    Dim LineNumber As Int = 0
  
    CustomListView1.DefaultTextColor = Colors.Black
  
    For I = 0 To L.Size - 1
        If A(I).Contains(SearchString) Then
            LineNumber = LineNumber + 1
            CustomListView1.AddTextItem(L.Get(I), LineNumber)
            Sleep(1)    'progressive update display
          
            If LineNumber >= 100 Then
                CustomListView1.AddTextItem("(more matches: refine search)", 999999)
                Exit
            End If
        End If
    Next
  
    If LineNumber = 0 Then
        CustomListView1.AddTextItem("(no matches found)", 999999)
    End If
  
End Sub

Sub EditText1_TextChanged (Old As String, New As String)
  
    LoadFirst100
  
End Sub
 
Upvote 0

emexes

Expert
Licensed User
4. The main problem is that it takes too long to create the lists. This means that you need to switch to lazy loading. It is quite simple to do and everything will happen very quickly.
Or just this.

Not sure if lazy loading here means (i) list continues to load in background, or (ii) list items are added only as they come into view, but... both methods should work.
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
The idea of loading only part of the list at startup or on demand, is not a bad idea, i try that a certain time ago using a Table, i was able to load let says the first 80 items, and display the table right away after the first 80 items load, and i was able to continue loading the table in background, but i don't remember how i did it, that was maybe 4 to 5 years ago, and it was an old version of B4A i think it was 6.?? or something, an i know i used the DoEvents to let control skip the loading of the list so it can display other stuff while it was loading in the background (Not realy another thread) but i did'nt have to swicth to another panel, once it was loaded it did not disapear, it was still available even if the app was put in the background. but we don't have the DoEvents anymore so the java runtime can lean control of a sub to do other message processing.

But even if i split it to many sections, it will still take some time to load another part. The think is trying to load it once and used that panel over and over again without reloading it.

I don't know if we could use a class and load the items in that class, and then trying to load part of the list on demand keeping pointers for top of list so if the user want to scroll up or down the list then only a small number of items (40 or 60 maybe even less) just to fill the start of visible item in the screen and scroll down so that when i reload the TOC on demand instead of reloading all the items we could simply load the last part of items that was displayed before the user switch to another section (Menu or Search) and then scrolling up or down the list and displaying only the few items at the time. But i don't know how to do that as we need to save the pointers of the list to the service module, and when the app resume, redisplay only those last items only

Here is a small sample of the loading process of that list
B4X:
' *******************************************************************
' load the Toc Table if not loaded
' this is where i call the loading of TOC table into the Drawer leftPanel
' *******************************************************************
Private Sub loadTocDrawerMenu(menuName As String, menuEvent As String)
    Dim isOpen As Boolean = False
    Dim menu As String = menuEvent
    'Log("Menu:" & menuEvent)
 Try   
    If Drawer.LeftOpen = True Then    ' the other mmenu is active close the screen
        Drawer.LeftOpen = False
        menu = Drawer.LeftPanel.tag
        'Log("Menu tag:" & Drawer.LeftPanel.Tag)
       isOpen = True
    Else
        menu = Drawer.LeftPanel.tag 
        isOpen = False
    End If
  Catch   
        Log("Drawer leftOpen: not Defined")
      End Try
    'Log("Drawer leftOpen:" & Drawer.leftOpen)
   
    If isOpen = False And menu <> menuEvent Then
        ProgressDialogShow("Chargement de la table des matières un instant..")
        Sleep(30)
        Drawer.LeftPanel.Color = Colors.white
        Drawer.LeftPanel.RemoveAllViews
        Drawer.LeftPanel.Top = 70Dip
        Drawer.LeftPanel.Width = deviceScreen.ScreenX - 1
        Drawer.LeftPanel.Height = deviceScreen.ScreenY - 125
        Drawer.LeftPanel.LoadLayout(menuName)
        Drawer.LeftPanel.Tag = menuEvent
        loadTocTable          ' load the TOC table Sub
        Drawer.LeftOpen = True
        TableView1.Base_Resize(Drawer.LeftPanel.Width,Drawer.LeftPanel.Height - 40)
        ProgressDialogHide
    Else
        Drawer.LeftOpen = True   
    End If
End Sub

'    Type Toc(Parent As String,nodeType As String,children As String,color As Long,Texte As String,Link As String,Title As String)

private Sub loadTocTable()
    ' set alpha indexes from content of toc table
    ' display the custom list views cards for toc content
    Dim c,d As ColorDrawable
        c.initialize2(Colors.White,0,0,Colors.Gray)
        d.Initialize2(Colors.White,0,0,Colors.Transparent)
        tocLabel.Gravity = Bit.Or(Gravity.CENTER, Gravity.TOP)
        tocLabel.TextSize = 28
        tocLabel.Text = "TABLE DES MATIERES"
        tocLabel.TextColor = Colors.black
        Drawer.LeftPanel.Tag = "toc"
     
   ' this load all the items from the list from Beginning to End
    For i = 0 To eServiceModule.tocList.Size-1
        Dim oPos As Toc     ' Defined user type
        oPos.Initialize
        oPos = eServiceModule.tocList.Get(i)
        Dim pnl As B4XView = Xui.CreatePanel("")
        pnl.SetLayoutAnimated(0, 0, 0, TableView1.AsView.Width, 61dip)
        pnl.LoadLayout("node")
        pnl.Color = Colors.white
        Textview1.Background = c
        Textview1.Color = Colors.white
        If oPos.nodeType="F" Then
            Textview1.Width = pnl.width
            TableView1.Add(pnl, oPos.link)
            Textview1.TextSize = 22
            Textview1.Tag = i
            Textview1.textColor = convertGreenColor(oPos.color)
            Textview1.Text = oPos.Texte
            Textview1.Gravity = Bit.Or(Gravity.CENTER, Gravity.TOP)
        Else if oPos.nodeType = "D" Then
            Textview1.Width = pnl.width
            TableView1.Add(pnl, oPos.link)
            Textview1.Tag = i
            Textview1.TextSize = 22
            Textview1.Padding = Array As Int(15dip,0,0,0)
            Textview1.TextColor = convertGreenColor(oPos.Color)
            Textview1.Text = Chr(0xF03C) & " " & oPos.Texte
            Textview1.Gravity = Bit.Or(Gravity.LEFT, Gravity.TOP)
        End If
    Next
 
End Sub

The eServiceModule.tocList contains all the items that will be loaded into the customListview (Named TableView1) and i used a user type named toc
So as you can see there is not too many processing to load that list into view i just stick to the essential .

But the idea of loading only small part is appealing, what will it take to load only a small part and keep pointers to know where we are in the list when it reload from app_resume, i have a service module loaded and is keep active even if the app is minimized so the list where are stored the items is always active until you realy close the App. So maybe if i create a class and define that class in the service module, and load everything into that class, that class will probably still be available as well as the pointers when the app resume. I don't know, the sub that load into the customListview will need to be part of the main activity, cause we can't load views from class or module outside the activity.

So i don't know if someone have an idea of how to acheive this, that will probably be a greater acheivement instead of reloading everything all the time... ?

PS: I use an app from my bank i just noticed that it does just that it only load a small amount of items, and when i scroll down it take a second or 2 to load another part of the list, but the first items that where loaded are still there, and if i go to another section, i still can go the the first list and the items are still there. I am wondering if using the B4XDrawer was a good idea from the beginning..

Any Idea ???
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
I followed up on lazy loading, tested this:

https://www.b4x.com/android/forum/t...ew-lazy-loading-virtualization.87930/#content

which is a modification of the example CardsList.zip at:

https://www.b4x.com/android/forum/threads/cards-list-with-customlistview.87720/#content

and... I'd have to say, it is not shabby. A list with 1000 items takes 703 ms to initially "load" on my $14.50 el crappo phone.

I was initially confused by the For I = 1 to 1000 loop, which looked like it was loading the CLV with panels per normal, but... I think they are just empty placeholder panels, which are then filled in when they are actually displayed.

Then I ran a bit further with it: changed it so that it only initially loaded up 10 CLV items, and then in the VisibleRangeChanged to loads up more items when it is getting near the bottom of the (current size of the) CLV. Since an item only takes a few ms to load, scrolling seems to still be full speed ie ~8 items/second... but, whilst admittedly these are larger items than your TOC entries, it's still going to take a quarter-hour for your app user to scroll to the bottom of your 6500 items.

Let's solve the startup time issue first, then we'll worry about how to quickly scroll within the CLV.
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
I'm Working on an idea to have the lsitview display only a part of the list on demand, and i think i'm on to something. I'll workit out and if this is succesfull i'll share the method that i use to accomplish that, maybe it will be to some value to others who want to accomplish what i'm trying to do,
I'll keep you posted...
Thanks.
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
Yes it is a Table of Content, but can be also any sort of list that you want to display in a CustomListView, and if my idea works, that will solve a lot of problems.

I already try a few test and loading 25 items ate the time is very fast, in fact from what i see so far, it only takes a few ms to load 25 new item into the list, so now i'm working on the pointers to keep track of the items displayed as we scroll. Too bad we don't have an event like ReachEnd but also an event ReachTop that could work the same way as the reachEnd but the opposite way.

All the trick is done in the reachEnd event so far. So i'll should be able to do this just have to spend some time on it... to test it properly
So Check out this tread it will be soon coming...
 
Upvote 0

emexes

Expert
Licensed User
if my idea works, that will solve a lot of problems
You could kludge a ReachEnd event by comparing the VisibleRangeChanged event's FirstIndex to 0 and LastIndex to CLV.Size.

To enable faster scrolling through the list: when the list is moving at speed, instead of loading *every* item, you could just load every 10th item or so (or however manyeth equates to scrolling top to bottom in 10 seconds, eg: if list has 10000 items, and is visually scrolling 15 items per second, then only display every 67th item).

The tricky bit will be to make sure to backfill missing items when scrolling stops.
 
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
Currently i'm using the ScrollChanged to determine if i'm scrolling up or i'm scrolling Down by storing the offset to a static var and comparing the current offset with the one saved, and it seem to be working. I can't use the first and last since these are not a good indicator as to know if i'm going down or Up. It is pretty much complicated to keep accurate pointers to show the list. What i want to do is only add new items to the listview at the end when scrolling down, but for the moment, i'm just playing with it in testing mode to find where the values are as to display the right items from the start up to the point where i'm am in the list, anyway this is work in progress, i'm not shure yet how i'm going to display the list, from the top index 0 up to Index xxxx, or just display a section of 25 items from xxxx to xxxx + 25, i don't now yet how this will turn out...

My best move will be to only display a window of 25 items at the time and use the same technique from scrolling up or scrolling down to load previous or next 25 items. it will all depend if i can make an accurate var to indicate the scrolling direction up Or Down.
will see !!!
 
Last edited:
Upvote 0

Jeanc161

Member
Licensed User
Longtime User
Well after testing many things with the customlistview events, i find that scrolling up or down in a controled fashion is not possible, so it is impossible to scroll thrue a small portion of a list, since we don't have an event like ReachTop and can't trigger a calculation to new up position.
But what i was able to do is loading a small portion of the list in the down scrolling action, and that works fine with litte latency (Just a few ms), the items loaded at a time is set to 20 items in my case. I use the ReachEnd event to trigger the calculation. Also i have to change the way i load the list for selecting only a small part of the list at a time.
See the Calculation routine..
B4X:
Sub TableView1_ReachEnd
    Log("Reach end of list")
    calculateTocPositionAndDisplay
End Sub

' This is where to calculate the part of the list that will be loaded when we reach the end of list (customListview)
Private Sub calculateTocPositionAndDisplay()
    Dim Start, Ending, Steps, Count As Int            ' those pointers are stored in service module
        Start = eServiceModule.BeginingListPointer     ' Pointer for beginning of index to be displayed from list
        Ending = eServiceModule.EndingListPointer    ' Pointer for ending of index to be displayed from list
        Steps = eServiceModule.itemsToLoad            ' Number of items to reload each time we scroll down
        Count = eServiceModule.tocList.Size-1        ' total items in the main list not ListView

' i keept these conditions in case i decided to do something else here     
 If scrollDirection = SCROLL_DIRECTION_DOWN Then
'     Log("SCrolling Down")
    Start = Start + Steps + 1
        If Start > Count Then
           Start = Count
            eServiceModule.BeginingListPointer = Start
        End If
        eServiceModule.BeginingListPointer = Start
    Ending = Start + Steps
    If Ending > Count Then
        Ending = Count  
            If eServiceModule.EndingListPointer < Count Then
               loadTocListPart(Start,Ending)
            End If  
            eServiceModule.EndingListPointer = Ending
    Else
        loadTocListPart(Start, Ending)
    End If
 Else if scrollDirection = SCROLL_DIRECTION_UP Then
    ' we could do something here
 End If      
 
End Sub

Also i test the event Scroll change event like this
The var scrollDirection is set in the Process_Globals as well as the
SCROLL_DIRECTION_DOWN and SCROLL_DIRECTION_UP that i use like a Constant var Declaration

B4X:
Sub TableView1_ScrollChanged (Offset As Int)
    Dim dipOffset As Int = 10    ' slight offset to test upon so a difference of 1 will not trigger
    If lastScrollOffset <= Offset Or (lastScrollOffset <= dipOffset And Offset <= dipOffset) Then
        scrollDirection = SCROLL_DIRECTION_DOWN      
        ' we are scrolling Down into the list
    Else
        scrollDirection = SCROLL_DIRECTION_UP
    End If  
        ' we are scrolling Up into the list
     ' the var lastScrollOffset is stored in the Process_Globals
    lastScrollOffset = Offset

End Sub
This little sub has tested to be quite accurate in telling me how i scroll the list either UP or DOWN and is triggered by the customListview on any change of position, using the offset of the list based on the position of the first visible item (I think).

Also the way to load the items has changed since i don't need to load everything at once, so i define 2 parameter as the Beginning and ending of the list to be displayed like

B4X:
Private Sub loadTocListPart(Begin As Int, Ending As Int)
    Dim c As ColorDrawable
    c.initialize2(Colors.White,0,0,Colors.Gray)
    tocLabel.Gravity = Bit.Or(Gravity.CENTER, Gravity.TOP)
    tocLabel.TextSize = 28
    tocLabel.Text = "TABLE DES MATIERES"
    tocLabel.TextColor = Colors.black
   
    For i = Begin To Ending  
        Dim oPos As Toc
        oPos.Initialize
        oPos = eServiceModule.tocList.Get(i)
        Dim pnl As B4XView = Xui.CreatePanel("")
        pnl.SetLayoutAnimated(0, 0, 0, TableView1.AsView.Width, 61dip)
        pnl.LoadLayout("node")
        pnl.Color = Colors.white
        Textview1.Background = c
        Textview1.Color = Colors.white
        If oPos.nodeType="F" Then
            Textview1.Width = pnl.width
            TableView1.Add(pnl, oPos.link)
            Textview1.TextSize = 22
            Textview1.Tag = i
            Textview1.textColor = convertGreenColor(oPos.color)
            Textview1.Text = oPos.Texte
            Textview1.Gravity = Bit.Or(Gravity.LEFT, Gravity.TOP)    ' change to gravity.CENTER after debuging
        Else if oPos.nodeType = "D" Then
            Textview1.Width = pnl.width
            TableView1.Add(pnl, oPos.link)
            Textview1.Tag = i
            Textview1.TextSize = 22
            Textview1.Padding = Array As Int(15dip,0,0,0)
            Textview1.TextColor = convertGreenColor(oPos.Color)
            Textview1.Text = Chr(0xF03C) & " " & oPos.Texte
            Textview1.Gravity = Bit.Or(Gravity.LEFT, Gravity.TOP)
        End If
    Next
End Sub

So if anybody want to use that example feel free to try it, i found this to be acceptable as it take much less time on startup of the app to be displayed and make the app look more responsive.

I you find another way to be able to scroll Up and Down and only display a small numbers of items in both direction, let me know i will test the solution cause i'm always looking for new solutions on lenghtly list to take less time to display (Specially in Android), it is not the same on IOS because the IOS in 64bits is very fast and take maybe 1 quarter less time to load and display.

So keep me posted if any new ideas come up

Thanks all.
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
Before I perhaps give it a go too:

what does one of your TOC entries look like?

are they always the same width and height?

are they always text?

could you post a screenshot of a TOC CLV? (image, not video)
 
Upvote 0
Top