iOS Question [solved] How to create asynchronous methods? (callSubDelayed)

yonson

Active Member
Licensed User
Longtime User
Hi Erel

in using the iHud library, I wish to show a busy message before starting on a long task, then remove it once its done.

In B4A, this is very straightforward - I just show the message, call 'DoEvents' and then call the method. Then 'DoEvents' again and then that works fine.

However, in B4i I'm aware this isn't possible. And so this simple operation doesn't work in sequence. Erel mentions in the iHud thread that in order to achieve this you must call an async method, but I was wondering if you could clarify how to create a async method?

Here is a very simple example. 'DoBusyEvent' is an example method which simply loops to represent a method which takes a long time to complete:-

B4X:
Dim hd As HUD
hd.ProgressDialogShow("busy")
DoBusyEvent
hd.ProgressDialogHide

Private Sub DoBusyEvent

For i=0 To 10000
Log(i)
Next
End Sub

how could I make 'DoBusyEvent' an async method, i.e. so that the busy message is shown whilst the method is running, and only closed once it is finished?
Many thanks!
 

yonson

Active Member
Licensed User
Longtime User
I use a range of different functions in my projects which I could consider slow, but typically these relate to processing database queries and rendering the results (which I do in code and not in the designer as they are dynamic).

The code above is simply an example, it loops from 1 to a high number outputting to the log. It serves to function but merely helps to illustrates the issue, which is where I want to:-

1. Show a busy message
2. run the 'slow' code
3. hide the busy message

Could you clarify how to make a method async, I assume the solution is to only call the 'hide message' by making the slow code call a job done or similar but I'm not sure how to implement this?
 
Upvote 0

yonson

Active Member
Licensed User
Longtime User
Hi Erel,

to be honest it would be outside the scope of the forum to post all the methods I've done, its a huge app and has thousands of lines of code, as I say in essence though it is running database calls (so using a wrapper class on DBUtils) and I render all the screens in code, not in the designer as they are dynamic.

I suppose what I need is a design pattern or something I could implement throughout.

If you run my simple example at the top of the page you'll see the issue. Could I ask - if you wanted to re-write this example so as to make it run properly, how would you go about it. i.e.

B4X:
Dim hd As HUD
' 1. show the busy dialog
hd.ProgressDialogShow("busy")
' 2. do something time consuming
DoBusyEvent
' 3. ONLY hide the busy dialog when complete
hd.ProgressDialogHide

Private Sub DoBusyEvent
For i=0 To 100000
Log(i)
Next
End Sub
 
Upvote 0

tucano2000

Active Member
Licensed User
Longtime User
Try this code. You can change TimerInterval and allow your application to perform other tasks.

B4X:
'Code module
#Region  Project Attributes
    #ApplicationLabel: B4i Example
    #Version: 1.0.0
    'Orientation possible values: Portrait, LandscapeLeft, LandscapeRight and PortraitUpsideDown
    #iPhoneOrientations: Portrait, LandscapeLeft, LandscapeRight
    #iPadOrientations: Portrait, LandscapeLeft, LandscapeRight, PortraitUpsideDown
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'Public variables can be accessed from all modules.
    Public App As Application
    Public NavControl As NavigationController
    Private Page1 As Page
    Private TimerDoBusyEvent  As Timer
    Private i As Int = 0
    Private hd As HUD
End Sub

Private Sub Application_Start (Nav As NavigationController)
    NavControl = Nav
    Page1.Initialize("Page1")
    Page1.Title = "Page 1"
    Page1.RootPanel.Color = Colors.White
    NavControl.ShowPage(Page1)

    Dim hd As HUD
    ' 1. show the busy dialog
    hd.ProgressDialogShow("busy")
    ' 2. do something time consuming
    'DoBusyEvent
    TimerDoBusyEvent.Initialize("DoBusyEvent",10)  'TimerInterval=10
    TimerDoBusyEvent.Enabled=True

    ' 3. ONLY hide the busy dialog when complete
    'hd.ProgressDialogHide
End Sub

Private Sub DoBusyEvent_Tick
    TimerDoBusyEvent.Enabled=False
    Log(i)
    i=i+1
    If i<=100000 Then TimerDoBusyEvent.Enabled=True Else hd.ProgressDialogHide
End Sub
 
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
It will not work. The timer event will only fire after the main thread exists the sub and returns to handle the internal message queue.

As I previously wrote there is no keyword equivalent to DoEvents in iOS. Also remember that you can only access the UI layer from the main thread, so running this method in a background thread will not help.

There are all kinds of possible solutions. For example you can split the large job into smaller jobs and use CallSubDelayed to execute the next sub-task each time.
 
Upvote 0

yonson

Active Member
Licensed User
Longtime User
thanks for your help Tucano - yes indeed as Erel says it does not work.

Essentially all I want to do is show the user that the app is busy 'doing something' whilst its carrying it out, essentially its a user feedback notification to announce that their input has been received, so its essential that its shown first.

Erel I've pasted in a real example from the code, with the show / hide messages embedded.

The important lines are

RenderUtils.showBusyMessage (MUST be shown first, to tell the user that we're busy doing something)

and

RenderUtils.hideMessage (MUST ONLY be called once everything is complete)

Any suggestions for how to modify for the callsubdelayed?

B4X:
'Class module
Sub Class_Globals
    Public thisPage As Page
    Dim header As HeaderBar
    Private scr_content As ScrollView
    Dim allGroups,allSubgroups,allFeatures As List

    Dim act_groups,act_subgroups,act_features As ActionSheet
    Dim pnl_groups,pnl_subgroups,pnl_features As Panel 
        Dim nearbys As List
     
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(Eventname As String)
   RenderUtils.showBusyMessage

thisPage.Initialize("pageNearby")
    thisPage.RootPanel.Color=Colors.Black

    allGroups = DatabaseInterface.getLutGroups
    allSubgroups = DatabaseInterface.getLutSubgroups
    allFeatures = DatabaseInterface.getFeaturesList
    renderHeaderPanel 
    refreshContent
 
End Sub

Public Sub closePage
 
    Main.NavControl.RemoveCurrentPage

End Sub


Private Sub pageSearch_Resize(Width As Int, Height As Int)
    refreshContent
End Sub

Private Sub refreshContent
    If scr_content.IsInitialized Then
        scr_content.RemoveViewFromParent
    End If
 
    scr_content.Initialize("",Main.screenWidth,Main.screenHeight-Main.headerBarHeight) ' set height of scrollview later
 
    ' *********************************
    ' ADD STUFF TO scr_content, then set height accordingly
    ' *********************************
 
    Dim pnl As Panel
  pnl = scr_content.Panel
 
 
    nearbys = DatabaseInterface.getNearbys
    Dim totalHeight As Int
    totalHeight=0
    '1. get all the favourites from the settings table
    nearbys = DatabaseInterface.getNearbys
    ' now add in others     
    For i = 0 To nearbys.Size-1
        Dim thisResult As Map
        thisResult = nearbys.Get(i)
        ' add in a panel for each with
        ' header (title and modifier button for increment, decrement & remove), and 'show all' button to do a search
        ' maximum 3 entries, or 1 entry of 'none' if not found
     
     
        Dim cp As Map
        cp = getNearbyPanelMap(thisResult)
'        Log("THIS CP HAS HEIHGT "&cp.Get("height"))
     
        pnl.AddView(cp.Get("panel"),0,totalHeight,Main.screenWidth,cp.Get("height"))

        totalHeight=totalHeight+cp.Get("height")
    Next 
 
    ' add on 3 dropdowns which will add them to the others
        pnl_groups.Initialize("pnl_groups")
        pnl_subgroups.Initialize("pnl_subgroups")
        pnl_features.Initialize("pnl_features")     
        pnl_groups=RenderUtils.renderShadowPanelWithClick(pnl_groups,"Add Nearest Group",Main.colour_white,"#000000",Main.colour_purple,Main.colour_lightgrey,Main.screenWidth,Main.screenWidth*0.15,Me)
        pnl_subgroups=RenderUtils.renderShadowPanelWithClick(pnl_subgroups,"Add Nearest Sub-Group",Main.colour_white,"#000000",Main.colour_purple,Main.colour_lightgrey,Main.screenWidth,Main.screenWidth*0.15,Me)
        pnl_features=RenderUtils.renderShadowPanelWithClick(pnl_features,"Add Nearest Feature",Main.colour_white,"#000000",Main.colour_purple,Main.colour_lightgrey,Main.screenWidth,Main.screenWidth*0.15,Me)     
        pnl.AddView(pnl_groups,0,totalHeight,Main.screenWidth,Main.screenWidth*0.15)
        totalHeight=totalHeight+(Main.screenWidth*0.15)
        pnl.AddView(pnl_subgroups,0,totalHeight,Main.screenWidth,Main.screenWidth*0.15)
        totalHeight=totalHeight+(Main.screenWidth*0.15)
        pnl.AddView(pnl_features,0,totalHeight,Main.screenWidth,Main.screenWidth*0.15)
        totalHeight=totalHeight+(Main.screenWidth*0.15)
    scr_content.ContentHeight=totalHeight
    thisPage.RootPanel.AddView(scr_content,0,Main.headerBarHeight,Main.screenWidth,Main.screenHeight-Main.headerBarHeight)


            RenderUtils.hideMessage
 

End Sub

Sub shadowPanel_Click
    act_groups.Initialize("act_filter","","Cancel","",MiscUtils.MapToList(allGroups,"full_value"))

    act_groups.Show(thisPage.RootPanel)
 
End Sub


Sub btn_settings_Click
    Dim p As Button
    p = Sender
    'Dim thisResult As Map
    'thisResult = p.Tag
        Log("trying to delete "&p.Tag)
        DatabaseInterface.nearbyDelete(p.Tag)
        refreshContent
 
End Sub

Sub btn_search_Click
   Dim p As Button
   p = Sender
    Dim thisResult As Map
    thisResult = p.Tag
 
    Dim restrictions As List
    restrictions.Initialize


    'Msgbox("locate id : "&refId,"title")
    Select Case thisResult.Get("type")
            Case "G"
                restrictions.Add("e.id=eg.establishment_id")
                restrictions.Add("e.id=et.establishment_id")
                restrictions.Add("et.type_id=t.id")         
                restrictions.Add("eg.group_id="&thisResult.Get("ref_id"))
                restrictions.Add("e.address_street=s.id") 
                SearchUtils.clearSearch
                SearchUtils.AddSearchElement("EG",thisResult.Get("ref_id"),"establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s",restrictions,"R")
                Dim ps As pageSearch
                ps.Initialize("")
                Main.NavControl.ShowPage(ps.thisPage)
            Case "S"
                restrictions.Add("e.id=eg.establishment_id")
                restrictions.Add("e.id=et.establishment_id")
                restrictions.Add("e.id=es.establishment_id")
                restrictions.Add("et.type_id=t.id")         
                restrictions.Add("es.subgroup_id="&thisResult.Get("ref_id"))
                restrictions.Add("e.address_street=s.id") 
                SearchUtils.clearSearch
                SearchUtils.AddSearchElement("ES",thisResult.Get("ref_id"),"establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s,establishment_subgroup es",restrictions,"R")
                Dim ps As pageSearch
                ps.Initialize("")
                Main.NavControl.ShowPage(ps.thisPage) 
            Case "F"
                restrictions.Add("e.id=eg.establishment_id")
                restrictions.Add("e.id=et.establishment_id")
                restrictions.Add("et.type_id=t.id")         
                restrictions.Add("ef.establishment_id=e.id")
                restrictions.Add("ef.feature_id="&thisResult.Get("ref_id"))
                restrictions.Add("e.address_street=s.id") 
                SearchUtils.clearSearch
                SearchUtils.AddSearchElement("EF",thisResult.Get("ref_id"),"establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s,establishment_feature ef",restrictions,"R")
                Dim ps As pageSearch
                ps.Initialize("")
                Main.NavControl.ShowPage(ps.thisPage)     
    End Select 
End Sub

Private Sub renderHeaderPanel 

    header.Initialize(Me,"Nearby",MiscUtils.HexToColor(Main.colour_purple,255),True,True,False,"","",Null,"","")
    thisPage.RootPanel.AddView(header.getPanelView, 0, 0, Main.screenWidth, Main.headerBarHeight)

End Sub


Private Sub act_groups_Click(item As String)
    For Each v As Map In allGroups
        If v.Get("full_value")=item Then
            Log("match for group, adding "&item)
            DatabaseInterface.nearbyAdd("G",v.Get("id"))
            refreshContent         
            Return
        End If
     
    Next
End Sub


Private Sub act_subgroups_Click(item As String)
    For Each v As Map In allSubgroups
        If v.Get("full_value")=item Then
            Log("match for subgroup, adding "&item)
            DatabaseInterface.nearbyAdd("S",v.Get("id"))
            refreshContent         
            Return
        End If
    Next
End Sub


Private Sub act_features_Click(item As String)
    Dim id As String
    id=DBUtils.GetTableValue(Main.SQL,"lut_feature","id","value='"&item&"'")
 
    If (IsNumber(id)) Then
        ' add
        DatabaseInterface.nearbyAdd("F",id)
        refreshContent 
    End If
 

End Sub


Sub pnl_groups_Click
    act_groups.Initialize("act_groups","","Cancel","",MiscUtils.MapToList(allGroups,"full_value"))

    act_groups.Show(thisPage.RootPanel)
End Sub

Sub pnl_subgroups_Click
    act_subgroups.Initialize("act_subgroups","","Cancel","",MiscUtils.MapToList(allSubgroups,"full_value"))

    act_subgroups.Show(thisPage.RootPanel)
End Sub

Sub pnl_features_Click
    act_features.Initialize("act_features","","Cancel","",MiscUtils.MapToList(allFeatures,"value"))

    act_features.Show(thisPage.RootPanel)
End Sub

Sub pnlButton_Click(callType As String,refId As String)
    ' add this favourite to search history, and open search activity
End Sub

Sub pnlButton_LongClick(p As PanelButton)
    ' might be one of the top row panels, if so ignore them (can't delete them)
 
    If (p=Null) Then Return
 

End Sub

' returnas a map, one contains the panel with all the results, other for the panel height
Private Sub getNearbyPanelMap(thisNearby As Map) As Map
    Dim l As Map
    l.Initialize
    Dim p As Panel
    p.Initialize("")
 
 
    Dim title As String
    title=DatabaseInterface.getNearbyTitle(thisNearby.Get("type"),thisNearby.Get("ref_id"))
    Dim pnl_title As Panel
    pnl_title=RenderUtils.renderSectionTitlePanel(title,Main.screenWidth*0.7,Main.screenWidth*0.15)
         
    Dim btn_settings,btn_search As Button 
 
    btn_settings.Initialize("btn_settings",btn_settings.STYLE_SYSTEM)
    Dim bitmapSettings As Bitmap
    bitmapSettings.Initialize(File.DirAssets,"btn_delete.png")
    RenderUtils.SetBackgroundImage(btn_settings,bitmapSettings,0)
    btn_settings.Tag=thisNearby.Get("id")

    btn_search.Initialize("btn_search",btn_search.STYLE_SYSTEM)
    Dim bitmapSearch As Bitmap
    bitmapSearch.Initialize(File.DirAssets,"btn_search.png")
        RenderUtils.SetBackgroundImage(btn_search,bitmapSearch,0)
    btn_search.Tag=thisNearby
 
    pnl_title.AddView(btn_settings,Main.screenWidth*0.7,0,Main.screenWidth*0.15,Main.screenWidth*0.15)
    pnl_title.AddView(btn_search,Main.screenWidth*0.85,0,Main.screenWidth*0.15,Main.screenWidth*0.15)

    ' put in splitter index
    Dim lbl_splitter1a,lbl_splitter1b,lbl_splitter2a,lbl_splitter2b As Label
    lbl_splitter1a.Initialize("")
    lbl_splitter1a.Color=MiscUtils.HexToColor("#f07fea",70)
    lbl_splitter2a.Initialize("")
    lbl_splitter2a.Color=MiscUtils.HexToColor("#f07fea",70)

    lbl_splitter1b.Initialize("")
    lbl_splitter1b.Color=MiscUtils.HexToColor("#460543",255)
    lbl_splitter2b.Initialize("")
    lbl_splitter2b.Color=MiscUtils.HexToColor("#460543",255)
 
    pnl_title.AddView(lbl_splitter1a,Main.screenWidth*0.7,3%x,1dip,(Main.screenWidth*0.15)-6%x) 
    pnl_title.AddView(lbl_splitter1b,(Main.screenWidth*0.7)-1dip,3%x,1dip,(Main.screenWidth*0.15)-6%x) 
    pnl_title.AddView(lbl_splitter2a,Main.screenWidth*0.85,3%x,1dip,(Main.screenWidth*0.15)-6%x) 
    pnl_title.AddView(lbl_splitter2b,(Main.screenWidth*0.85)-1dip,3%x,1dip,(Main.screenWidth*0.15)-6%x)     
 
    p.AddView(pnl_title,0,0,100%x,Main.screenWidth*0.15)
    p.AddView(btn_settings,Main.screenWidth*0.7,0,Main.screenWidth*0.15,Main.screenWidth*0.15)
    p.AddView(btn_search,Main.screenWidth*0.85,0,Main.screenWidth*0.15,Main.screenWidth*0.15)


 
    Dim height As Int
    height=Main.screenWidth*0.15
 
    Dim rowHeight As Int
    rowHeight=Main.screenWidth*0.2
 
    ' now add in results
    ' now need to be a bit cute here. I want to show closest 3 places, but the closest might be shut, so limit to first 10 only
    ' and that'll do it I reckon

    Dim resultsPanelButtons As List
    resultsPanelButtons.Initialize
    resultsPanelButtons=runSearch(thisNearby.Get("type"),thisNearby.Get("ref_id"),Main.nearby_limit)

    If (resultsPanelButtons.Size<1) Then
            Dim lbl_noresults As Label
            lbl_noresults=RenderUtils.renderLabel("Nothing Nearby")         
            lbl_noresults.Color=Colors.Black
            p.AddView(lbl_noresults,0,height,Main.screenWidth,rowHeight)
            height = height+rowHeight
    Else
        'Dim total As Int
        'total=resultsPanelButtons.Size
        For Each pb As PanelButton In resultsPanelButtons
            p.AddView(pb.Render,0,height,Main.screenWidth,rowHeight)
            height = height+rowHeight
            'height=15%x+(total*rowHeight)
         
        Next
     
        'p.AddView(renderUltimateListView(resultsInfo),0,15%x,100%x,height-15%x)
    End If
     
        l.Put("panel",p) 
        l.Put("height",height)
     
    Return l
End Sub

' return panels in 'panel' map, and height in 'height' value
' returns LIST OF DATA
Private Sub runSearch(nearbyType As String,Search_id As String, number As Int) As List 
    Dim centreLocation As Location
    centreLocation.Initialize2(Main.currentLocation.Latitude,Main.currentLocation.Longitude)


    Dim restrictions As List
    restrictions.Initialize
    Dim q As String

    Dim tables,search_type As String

    ' if establishment_id is set, then use that as the starting lat/lon,
    ' else if traveller_point_id is set, use that
    ' sle Else use current Location
    If (Main.establishment_id<>Null AND IsNumber(Main.establishment_id) AND Main.establishment_id>0) Then
        centreLocation=EstablishmentInterface.getEstablishmentLocation(Main.establishment_id)     
    Else If (Main.traveller_point_id<>Null  AND IsNumber(Main.traveller_point_id) AND Main.traveller_point_id>0) Then
        centreLocation.Initialize2(Main.mapCentre_lat,Main.mapCentre_lon)
    Else
        centreLocation=Main.currentLocation
    End If

    'Log("NEARBY SEARCHING FOR "&nearbyType)

    If (nearbyType="G") Then
        restrictions.Add("e.id=eg.establishment_id")
        restrictions.Add("e.id=et.establishment_id")
        restrictions.Add("et.type_id=t.id")         
        restrictions.Add("eg.group_id="&Search_id)
        restrictions.Add("e.address_street=s.id") 
        tables="establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s"
        search_type="EG"

    Else If (nearbyType="S") Then
            restrictions.Add("e.id=et.establishment_id")
            restrictions.Add("e.id=eg.establishment_id")
            restrictions.Add("e.id=es.establishment_id")
            restrictions.Add("et.type_id=t.id")         
            restrictions.Add("es.subgroup_id="&Search_id)
            restrictions.Add("e.address_street=s.id") 
            tables="establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s,establishment_subgroup es"
            search_type="ES"
    Else If (nearbyType="F") Then
            restrictions.Add("e.id=et.establishment_id")
            restrictions.Add("e.id=eg.establishment_id")
            restrictions.Add("et.type_id=t.id")         
            restrictions.Add("ef.establishment_id=e.id")
            restrictions.Add("ef.feature_id="&Search_id)
            restrictions.Add("e.address_street=s.id")     
            tables="establishment e,establishment_type et, establishment_group eg,lut_type t,lut_street s,establishment_feature ef"
            search_type="EF"
    End If
    Dim s As Search
    s.Initialize(Me,search_type,Search_id,tables,restrictions,"R",centreLocation,number)
    'Return s.RowInfos
    Dim result_panels As List
    result_panels=s.getResultPanels 'return panelButtons for each result 
    Return result_panels
End Sub
 
Upvote 0

yonson

Active Member
Licensed User
Longtime User
Hi Erel,

I think that will work perfectly, thank you, so in my initial code:-

B4X:
    CallSubDelayed(Me,"renderHeaderPanel")  
    CallSubDelayed(Me,"refreshContent")

instead of directly calling the subs. I'll let you know if I run into anymore issues
 
Upvote 0
Top