B4A Library [Class] SearchView - More powerful alternative to AutoCompleteEditText

Status
Not open for further replies.
Edit: better to use B4XDialog + B4XSearchTemplate

SearchView is made of an EditText and ListView. When the user enters text into the EditText the ListView shows the items that start with this text or that contain the text (in this order).

This view is useful for allowing the user to select an item from many items.

Advantages over AutoCompleteEditText:
  • SearchView uses an internal index that is built when you call SetItems. This allows it to quickly find the matches.
  • SearchView also shows items that contain the input text (not just prefixes).
  • The class code can be further customized as needed.

upload_2017-2-21_17-48-19.png


Tutorial about handling large, searchable lists: https://www.b4x.com/android/forum/t...e-list-with-searchview-b4xserializator.61872/
 

Attachments

  • SearchView.zip
    45.1 KB · Views: 2,094
Last edited:

electronik54

Member
Licensed User
Longtime User
i have already made a layout with autocomplete box in it. in your example you connected autocomplete with a text file, how do i do that with a DB?
 

MaxApps

Active Member
Licensed User
Longtime User
Hi Erel

Is it possible to have the whole list shown and then the list grow smaller, as you type in the search?

Kind regards
Jakob
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
- Add a call to et_TextChanged in AddToParent.
- Add the following code to et_TextChanged:
B4X:
'Adds the view to the parent. The parent can be an Activity or Panel.
Public Sub AddToParent(Parent As Panel, Left As Int, Top As Int, Width As Int, Height As Int)
   Parent.AddView(et, Left, Top, Width, 60dip)
   Parent.AddView(lv, Left, Top + et.Height, Width, Height - et.Height)
   et_TextChanged("", "")
End Sub

Private Sub lv_ItemClick (Position As Int, Value As Object)
   et.Text = Value
   et.SelectionStart = et.Text.Length
   IME.HideKeyboard
   lv.Visible = False
   If SubExists(mCallback, mEventName & "_ItemClick") Then
      CallSub2(mCallback, mEventName & "_ItemClick", Value)
   End If
End Sub

Private Sub et_TextChanged (Old As String, New As String)
   lv.Clear
   If lv.Visible = False Then lv.Visible = True
   If New.Length < MIN_LIMIT Then 
      AddItemsToList(allItems, "")
      Return
   End If
 

MaxApps

Active Member
Licensed User
Longtime User
Ok. I figured it out.

In Sub Class_Globals Add Private allItems As List
In Public Sub AddToParent(Parent As Panel, Left As Int, Top As Int, Width As Int, Height As Int) add et_TextChanged("", "")
In Public Sub SetItems(Items As List) As Object add allItems = Items and AddItemsToList(allItems, "")

B4X:
Sub Class_Globals
   Private prefixList As Map
   Private substringList As Map
   Private et As EditText
   Private lv As ListView
   Private MIN_LIMIT, MAX_LIMIT As Int
   MIN_LIMIT = 1
   MAX_LIMIT = 4 'doesn't limit the words length. Only the index.
   Private mCallback As Object
   Private mEventName As String
   Private allItems As List
End Sub

Public Sub AddToParent(Parent As Panel, Left As Int, Top As Int, Width As Int, Height As Int)
   Parent.AddView(et, Left, Top, Width, 60dip)
   Parent.AddView(lv, Left, Top + et.Height, Width, Height - et.Height)
   et_TextChanged("", "")
End Sub

Public Sub SetItems(Items As List) As Object
   Dim startTime As Long 
   startTime = DateTime.Now
   ProgressDialogShow2("Building index...", False)
   Dim noDuplicates As Map
   noDuplicates.Initialize
   prefixList.Clear
   substringList.Clear
   Dim m As Map
   Dim li As List
   For i = 0 To Items.Size - 1
      If i Mod 100 = 0 Then DoEvents
      Dim item As String
      item = Items.Get(i)
      item = item.ToLowerCase
      noDuplicates.Clear
      For start = 0 To item.Length
         Dim count As Int
         count = MIN_LIMIT
         Do While count <= MAX_LIMIT AND start + count <= item.Length
            Dim str As String
            str = item.SubString2(start, start + count)
            If noDuplicates.ContainsKey(str) = False Then 
               noDuplicates.Put(str, "")
               If start = 0 Then m = prefixList Else m = substringList
               li = m.Get(str)
               If li.IsInitialized = False Then
                  li.Initialize
                  m.Put(str, li)
               End If
               li.Add(Items.Get(i)) 'Preserve the original case
            End If
            count = count + 1
         Loop
      Next
   Next
   allItems = Items
   AddItemsToList(allItems, "")

   ProgressDialogHide
   Log("Index time: " & (DateTime.Now - startTime) & " ms (" & Items.Size & " Items)")
   Return Array As Object(prefixList, substringList)
End Sub

and... voila... :)

Kind regards
Jakob
 

devlei

Active Member
Licensed User
Longtime User
I have an existing Activity with a Listview that is populated from a sqlite db.

I would like to be able to type into an EditText above the list to search the list and display matching items in the same existing list. Similar to what the Android Contacts app does!

Can I use the SearchView Class to do this, and if so, can you give me some pointers?
 

devlei

Active Member
Licensed User
Longtime User
Thank you, Erel.

If I want the SearchView's listview to look the same as other listviews in my app, must this be done by modifying the lv in the Class?

My initial list is populated from the db using AddTwoLines2 where the Return value is the ID. How will I access this ID value from the SearchView's list item selected?
 

devlei

Active Member
Licensed User
Longtime User
This SearchView Class is really great!! However, I need more help with something.

I use the SearchView to quickly find items matching my search, then click on an item to open another Activity which is a form so that I can edit that row of the DB. After saving the changes on the form, that Activity closes and it returns to the 1st Activity with the SearchView. On returning I would like to show the same list with same search text in the EditText, but that is updated according to the changes made.

Also, after a search, I would like to be able to LongClick an item in the ListView to delete that item from the DB, then update the Listview with the same search current.

Note: I have added the call to the et_TextChanged to show the full list before typing in the et according to Erel's response in Post #88, but can't see how to achieve this on returning to the Activity after editing the row in the Activity with the form.

Any help will be much appreciated,
Dave
 

Richard Goh

Active Member
Licensed User
Longtime User
I had below error after I added SearchView function into my program. My program using AnotherDatePicker function as well with no problem prior to add the SearchView. How can I solve this problem?


anotherdatepicker_show (B4A line: 173)
holder.Visible = True
java.lang.RuntimeException: Object should first be initialized (Panel).
at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:46)
at anywheresoftware.b4a.objects.ViewWrapper.setVisible(ViewWrapper.java:208)
at b4a.myexpenses.anotherdatepicker._show(anotherdatepicker.java:754)
at b4a.myexpenses.anotherdatepicker._lbl_click(anotherdatepicker.java:644)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:63)
at android.view.View.performClick(View.java:4262)
at android.view.View$PerformClick.run(View.java:17351)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4935)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
at dalvik.system.NativeStart.main(Native Method)
java.lang.RuntimeException: Object should first be initialized (Panel).
** Activity (main) Pause, UserClosed = false **
 

katrad

Member
Licensed User
Longtime User
Im having a strange problem, I'm using this to search locations. In this particular case I'm searching for "TX" and it shows the first 14 and then stops. (there are actually 31 locations). It appears to work fine when I search for other States, but, for some reason not Texas. I've checked the data to see if there was something strange, but, I dont see anything and wondered if you could see something I don't, or what I might be doing wrong. -thanks in advance.

Heres my code for loading the data for the search
B4X:
Sub GetSearchData
  Dim cities As List
  Dim CCData As Cursor
    CCData = SQL.ExecQuery("Select Local, Local_City, Local_St, id, Lat, Long from LUDetail order by local*1, local")
    If CCData.IsInitialized Then
      cities.Initialize
      For Row = 0 To CCData.RowCount -1
          CCData.Position = Row
          cities.Add(s.Trim(CCData.GetString("Local")) & "-" & CCData.GetString("Local_City") & " " & CCData.GetString("Local_St"))
      Next
      index = sv.SetItems(cities)
        '
        LV.Clear
        For l = 0 To cities.Size -1
          LV.AddSingleLine(cities.get(l))
        Next      
    End If
End Sub

You can ignore the last listview, I'm using that on another page for browsing the data. I'm using the SearchView unmodified.
 
Status
Not open for further replies.
Top