B4J Tutorial [BANano] Lessons I learned from building a Background Worker in a BANanoServer Project

Hi Fam

V7 of BANano came with background workers. I tested this and I was happy recently to actually use them in a solution.


The beaty of these gems is them not affecting the main thread. This means they can help in speeding up your app. The UI wont be hogged as long processes will run in the background. Its multi-threading your app. These lessons are personal to my experience and I could be wrong and stand to be corrected.

Lesson 1 - A webworker is an app and cannot reference b4xlib

A webworker is a class object. So I thought I could add another class in my project and or refer to a b4xlib that the web worker will use, This did not work. It seems the web worker code should be self encompassing, i.e. all the code that it will use should be in it. For me this felt like a redundance as I had to use code that I already have in my project, but then I shifted my thought to think of it as an independent app. Yes, mine uses BANanoFetch but it just wasnt able to use stuff in my b4xlib without a compilation error.

So all the code to execute fetches and all that was later inside the web worker.

Lesson 2: Some variables wont work if you put them on class globals

As my web worker was doing fetch calls, I hoped that defining a BANanoFetch object in class globals to be referenced anywhere would work. It did not. So define most variables locally where you use them.

Lesson 3: The process flow of the Background Worker.

3.1 Add the background worker in Main > App Start before building your app.

B4X:
Server.BANano.AddBackgroundWorker("worker", "BROWSERGreenAPIWorker28")

3.2 Start the Background Worker in BROWSERAPP. Initialize and no where else. This is the main thread of the app it seems. You cannot start the worker from a button click event.

B4X:
BANano.StartBackgroundWorker("worker", Null)

3.3. If you need to call the Background Worker anywhere in your app, call it via the BROWSERAPP. So anywhere in my app I call Main.MyApp.GetGreenApiContacts. The GetGreenApiContacts is a subroutine inside my background worker, however I also have a sub in my BROWSER app of the same name to call the one inside the background worker that does everything I need

GetGreenApiContacts in BROWSERAPP that calls the background worker

B4X:
Sub GetGreenApiContacts
    BANano.RunBackgroundWorkerMethod("worker", "GetGreenApiContacts", "GetGreenApiContacts", Array(GA_Instance, GA_Token, DBAddress))
End Sub

GetGreenApiContacts inside Backround Worker that does the work

B4X:
Sub GetGreenApiContacts(sInstance As String, sToken As String, sDBAddress As String)
    'get current chats, if one exist, we will update
    Dim chats As Map = CreateMap()
    'create the contact in the contacts table
    FetchInitialize($"${sDBAddress}/records/chats"$)
    SetContentTypeApplicationJSON
    BANano.Await(fetchit("GET"))
    If Response.ContainsKey("records") Then
        Dim result As List = Response.Get("records")
        For Each rec As Map In result
            Dim schatid As String = rec.Get("chatid")
            Dim sid As String = rec.Get("id")
            chats.Put(schatid, sid)
        Next
    End If
....
BANano.SendFromBackgroundWorker("GetGreenApiContacts", "Done", Null)
[/code]

3.4 The data sent from the background worker must be received by BROWSERAPP. So when my background worker sub finishes, it sends a message to the main thread.

The ending line of my GetGreenApiContacts sub inside the background worker sub-routine has this added to it.

B4X:
BANano.SendFromBackgroundWorker("GetGreenApiContacts", "Done", Null)

This will then ensure that the message is sent and is trapped by the following call in BROWSERApp.

B4X:
public Sub BANano_MessageFromBackgroundWorker(WorkerName As String, Tag As String, Value As Object, Error As Object)                'ignoredeadcode
    ' the Tag will define the type of message send by the Background Worker
    Select Case Tag
    Case "GetGreenApiContacts"
'        'we are getting contacts from greenapi
'        Dim sjson As String = BANano.ToJson(Value)
'        App.DownloadTextFile(sjson, "conversations.json")
        App.ShowToastSuccess($"Contacts have been extracted and updated."$)        
    End Select
End Sub
 
  • Like
Reactions: byz

Mashiane

Expert
Licensed User
Longtime User
Background Worker Code that execute Fetch API calls using BANanoFetch

This is just for basic understanding of how everything got to work for me... Most of the code inside here is the SDUIFetch source code

B4X:
'This is a BANano Background worker template class 
#IgnoreWarnings:12
Sub Class_Globals
    Private BANano As BANano 'ignore
    Private fetchError As Object
    Private bfo As BANanoFetchOptions
    Private Response As Map
    Private sgreenURL As String = "https://api.green-api.com"
    Private schema As Map
    Private const DB_BOOL As String = "BOOL"
    Private const DB_INT As String = "INT"
    Private const DB_STRING As String = "STRING"
    Private const DB_DOUBLE As String = "DOUBLE"
    Private headers As Map
    Private parameters As Map
    Private Success As Boolean
    Private mError As String
    Private OK As Boolean = False
    Private Status As String = ""
    Private StatusText As String = ""
    Private vbaseURL As String
    Private vdata As Map
    Private formData As BANanoObject
    Private GetHeadersFromFormData As Boolean = False
    Private bHasFormData As Boolean = False
    Private PostDataAsSearchParams As Boolean = False
    Private Body As String
    Private NoCors As Boolean = False
    Private NoCache As Boolean = False
    Private ReferrerPolicy As String = ""
    Private Redirect As String = ""
End Sub

' can have additional parameters
Public Sub Initialize()
    ' additional javscript needed in the Worker
    ' THESE CAN NOT CONTAIN JAVASCRIPT CODE THAT MANUPULATE THE DOM
    ' BANano.DependsOnAsset("myCode.js")
End Sub

Public Sub BANano_StopBackgroundWorker()
'    BANano.SendFromBackgroundWorker("Stopped", Null, Null)
End Sub

Private Sub FetchInitialize(url As String)
    headers.Initialize
    parameters.Initialize
    schema.Initialize
    bfo.Initialize
    vbaseURL = url
    vdata.Initialize 
    Body = ""
    GetHeadersFromFormData = False
    bHasFormData = False
    NoCors = False
    NoCache = False
    ReferrerPolicy = ""
    Redirect = ""
End Sub

Sub GetGreenApiContacts(sInstance As String, sToken As String, sDBAddress As String)
    'get current chats, if one exist, we will update
    Dim chats As Map = CreateMap()
    'create the contact in the contacts table
    FetchInitialize($"${sDBAddress}/records/chats"$)
    SetContentTypeApplicationJSON
    BANano.Await(fetchit("GET"))
    If Response.ContainsKey("records") Then
        Dim result As List = Response.Get("records")
        For Each rec As Map In result
            Dim schatid As String = rec.Get("chatid")
            Dim sid As String = rec.Get("id")
            chats.Put(schatid, sid)
        Next
    End If
    ......
    BANano.SendFromBackgroundWorker("GetGreenApiContacts", "Done", Null)
End Sub

'set content type application json
private Sub SetContentTypeApplicationJSON
    SetContentType("application/json; charset=utf-8")
End Sub

'set the content type
private Sub SetContentType(value As String)
    AddHeader("Content-Type", value)
End Sub

Private Sub fetchit(method As String)
    Dim bf As BANanoFetch
    Dim bfr As BANanoFetchResponse
    'reset some variables
    Success = False
    mError = ""
    OK = False
    Status = ""
    StatusText = ""
    Response.Initialize
    Dim xbaseURL As String = vbaseURL
    bfo.Method = method
    If Redirect <> "" Then
        bfo.SetField("redirect", Redirect)
    End If
    If NoCors Then
        bfo.Mode = "no-cors"
    End If
    If NoCache Then
        bfo.cache = "no-store"
    Else
        bfo.cache = "no-cache"
    End If
    If headers.Size <> 0 Then
        bfo.Headers = headers
    End If
    '
    If vdata.Size <> 0 Then
        Dim jsonData As String = BANano.ToJson(vdata)
        bfo.Body = jsonData
        If PostDataAsSearchParams Then
            Dim obj As BANanoObject
            obj.Initialize2("URLSearchParams", vdata)
            bfo.Body = obj
        End If
    Else
        bfo.Body = Null
    End If
    'set our own body
    If Body <> "" Then bfo.Body = Body
    If bHasFormData Then
        bfo.Body = formData
        If GetHeadersFromFormData Then
            bfo.Headers = formData.RunMethod("getHeaders", Null).result
        End If
    End If
    
    If parameters.Size <> 0 Then
        Dim sparameters As String = URLQueryStringFromMap(parameters)
        xbaseURL = $"${vbaseURL}?${sparameters}"$
    End If
    '
    If ReferrerPolicy <> "" Then
        bfo.ReferrerPolicy = ReferrerPolicy
    End If
    'use local variables
    Dim prom As BANanoPromise
    Dim bfe As Object
    prom.NewStart
    bf.Initialize(xbaseURL, bfo)
    bf.Then(bfr)
    Status = bfr.status
    StatusText = bfr.StatusText
    OK = bfr.ok
    Success = bfr.OK
    If bfr.Status = 200 Then
        Log(StatusText)
        BANano.ReturnThen(bfr.Json)
    Else
        Dim m As Map = CreateMap()
        If StatusText.StartsWith("{") Then
            m = BANano.FromJson(StatusText)
        else if StatusText.StartsWith("[") Then
            m = BANano.FromJson(StatusText)
        Else
            m.Put("status", StatusText)
        End If
        BANano.ReturnThen(m)
    End If
    bf.Else(bfe)
    'network errors
    mError = bfe
    BANano.ReturnElse(bfe)
    bf.End
    prom.NewEnd
    '
    Response = BANano.Await(prom)
End Sub

private Sub URLQueryStringFromMap(params As Map) As String
    Dim sb As StringBuilder
    sb.Initialize
    For Each k As String In params.Keys
        Dim v As String = params.Get(k)
        k = BANano.EncodeURIComponent(k)
        v = BANano.EncodeURIComponent(v)
        sb.Append($"${k}=${v}&"$)
    Next
    Dim sout As String = sb.ToString
    sb = Null
    sout = RemDelim(sout, "&")
    Return sout
End Sub

private Sub RemDelim(sValue As String, Delim As String) As String
    Dim sw As Boolean = sValue.EndsWith(Delim)
    If sw Then
        Dim lDelim As Int = Delim.Length
        Dim nValue As String = sValue
        sw = nValue.EndsWith(Delim)
        If sw Then
            nValue = nValue.SubString2(0, nValue.Length-lDelim)
        End If
        Return nValue
    Else
        Return sValue
    End If
End Sub

'this appears on query string 
private Sub AddParameter(k As String, v As String)
    Dim dt As String = schema.Get(k)
    Select Case dt
        Case DB_BOOL
            v = CBool(v)
        Case DB_INT
            v = CInt(v)
        Case DB_STRING
            v = CStr(v)
        Case DB_DOUBLE
            v = CDbl(v)
    End Select
    parameters.Put(k, v)
End Sub

private Sub SchemaClear
    schema.Initialize
End Sub

private Sub SchemaAddDouble(bools As List)
    For Each b As String In bools
        schema.Put(b, DB_DOUBLE)
    Next
End Sub

private Sub SchemaAddBoolean(bools As List)
    For Each b As String In bools
        schema.Put(b, DB_BOOL)
    Next
End Sub

private Sub SchemaAddIntegers(bools As List)
    For Each b As String In bools
        schema.Put(b, DB_INT)
    Next
End Sub

private Sub SchemaAddString(bools As List)
    For Each b As String In bools
        schema.Put(b, DB_STRING)
    Next
End Sub

'add a header value
private Sub AddHeader(prop As String, value As String)
    headers.Put(prop, value)
End Sub

'add a parameter value
private Sub AddParameters(mapOf As Map)
    For Each k As String In mapOf.Keys
        Dim v As Object = mapOf.Get(k)
        AddParameter(k, v)
    Next
End Sub

'double
private Sub CDbl(o As String) As Double
    Dim nvalue As String = CStr(o)
    nvalue = nvalue.replace(",", ".")
    o = Val(nvalue)
    Dim nout As Double = BANano.parseFloat(o)
    Return nout
End Sub

'convert object to string
private Sub CStr(o As Object) As String
    If BANano.isnull(o) Or BANano.IsUndefined(o) Then o = ""
    If o = "null" Then Return ""
    If o = "undefined" Then Return ""
    Return "" & o
End Sub


private Sub Val(value As String) As String
    value = CStr(value)
    value = value.Trim
    If value = "" Then value = "0"
    Try
        Dim sout As String = ""
        Dim mout As String = ""
        Dim slen As Int = value.Length
        Dim i As Int = 0
        For i = 0 To slen - 1
            mout = value.CharAt(i)
            If InStr("0123456789.-", mout) <> -1 Then
                sout = sout & mout
            End If
        Next
        Return sout
    Catch
        Return value
    End Try
End Sub


private Sub InStr(sText As String, sFind As String) As Int
    Return sText.tolowercase.IndexOf(sFind.tolowercase)
End Sub

'parseBool
private Sub CBool(v As Object) As Boolean
    If BANano.IsNull(v) Or BANano.IsUndefined(v) Then
        v = False
    End If
    If GetType(v) = "string" Or GetType(v) = "object" Then
        Dim s As String = v & ""
        s = s.tolowercase
        s = s.trim
        If s = "" Then Return False
        If s = "false" Then Return False
        If S = "true" Then Return True
        If s = "1" Then Return True
        If s = "y" Then Return True
        If s = "0" Then Return False
        If s = "n" Then Return False
        If s = "no" Then Return False
        If s = "yes" Then Return True
        If s = "on" Then Return True
        If s = "off" Then Return False
    End If
    Return v
End Sub

'create a new formdata
private Sub NewFormData
    formData.Initialize2("FormData", Null)
    bHasFormData = True
End Sub

private Sub AddFormData(fldName As String, fldValue As Object)
    formData.RunMethod("set", Array(fldName, fldValue))
    bHasFormData = True
End Sub

private Sub AddData(k As String, v As String)
    Dim dt As String = schema.Get(k)
    Select Case dt
        Case DB_BOOL
            v = CBool(v)
        Case DB_INT
            v = CInt(v)
        Case DB_STRING
            v = CStr(v)
        Case DB_DOUBLE
            v = CDbl(v)
    End Select
    PutRecursive(vdata, k, v)
End Sub

private Sub PutRecursive(xdata As Map, path As String, value As Object)
    Try
        If BANano.IsNull(path) Or BANano.IsUndefined(path) Then
            path = ""
        End If
        If path = "" Then Return
        Dim prevObj As BANanoObject = xdata
        If path.IndexOf(".") = -1 Then
            'we dont have a dot
            prevObj.SetField(path, value)
        Else
            'we have a dot
            Dim items As List = BANano.Split(".", path)
            Dim iTot As Int = items.Size
            Dim iCnt As Int
            '
            Dim strprev As String = ""
            Dim prtObj As BANanoObject
            Dim litem As String = items.Get(iTot - 1)
            '
            For iCnt = 1 To iTot - 1
                'get the previos path
                strprev = items.Get(iCnt - 1)
                'the parent object
                prtObj = prevObj.GetField(strprev)
                'this does not exist, create it
                If BANano.IsUndefined(prtObj) Then
                    Dim no As Object
                    prevObj.SetField(strprev, no)
                    prevObj = prevObj.GetField(strprev)
                Else
                    prevObj = prtObj
                End If
            Next
            prevObj.SetField(litem, value)
        End If
    Catch    'ignore
    End Try    'ignore
End Sub

'get recursive data from a map
private Sub GetRecursive(xdata As Map, path As String) As Object
    Try
        Dim prevObj As BANanoObject = xdata
        If path.IndexOf(".") = -1 Then
            Dim res As Object = prevObj.GetField(path)
            If BANano.IsUndefined(res) Then
                res = Null
            End If
            Return res
        Else
            Dim items As List = BANano.Split(".", path)
            Dim iTot As Int = items.Size
            Dim iCnt As Int
            '
            Dim strprev As String = ""
            Dim prtObj As BANanoObject
            Dim litem As String = items.Get(iTot - 1)
            '
            For iCnt = 1 To iTot - 1
                'get the previos path
                strprev = items.Get(iCnt - 1)
                'the parent object
                prtObj = prevObj.GetField(strprev)
                'this does not exist, return
                If BANano.IsUndefined(prtObj) Then
                    Return Null
                Else
                    prevObj = prtObj
                End If
            Next
            Dim res As Object = prevObj.GetField(litem)
            If BANano.IsUndefined(res) Then
                res = Null
            End If
            Return res
        End If
    Catch
        Return Null
    End Try
End Sub

'convert to int
private Sub CInt(o As Object) As Int
    o = Val(o)
    Return BANano.parseInt(o)
End Sub

private Sub MvField(sValue As String, iPosition As Int, Delimiter As String) As String
    If sValue.Length = 0 Then Return ""
    Dim xPos As Int = sValue.IndexOf(Delimiter)
    If xPos = -1 Then Return sValue
    Dim mValues As List = StrParse(Delimiter,sValue)
    Dim tValues As Int
    tValues = mValues.size -1
    Select Case iPosition
        Case -1
            Return mValues.get(tValues)
        Case -2
            Return mValues.get(tValues - 1)
        Case -3
            Dim sb As StringBuilder
            sb.Initialize
            Dim startcnt As Int
            sb.Append(mValues.Get(1))
            For startcnt = 2 To tValues
                sb.Append(Delimiter)
                sb.Append(mValues.get(startcnt))
            Next
            Dim sout As String = sb.ToString
            sb.Initialize
            Return sout
        Case Else
            iPosition = iPosition - 1
            If iPosition <= -1 Then
                Return mValues.get(tValues)
            End If
            If iPosition > tValues Then
                Return ""
            End If
            Return mValues.get(iPosition)
    End Select
End Sub

private Sub StrParse(delim As String, inputString As String) As List
    Dim nl As List
    nl.Initialize
    Try
        inputString = CStr(inputString)
        If BANano.IsNull(inputString) Or BANano.IsUndefined(inputString) Then inputString = ""
        If inputString = "" Then Return nl
        If inputString.IndexOf(delim) = -1 Then
            nl.Add(inputString)
        Else
            nl = BANano.Split(delim,inputString)
        End If
        Return nl
    Catch
        'Log(LastException)
        Return nl
    End Try
End Sub

'set data from a map
private Sub SetData(m As Map)
    For Each k As String In m.Keys
        Dim v As Object = m.Get(k)
        AddData(k, v)
    Next
End Sub

'convert unix time to normal date
Sub UnixToNormalDate(udt As String) As String
    Dim res As String = BANano.RunJavascriptMethod("unixToNormalDate", Array(udt))
    Return res
End Sub

#if javascript
function unixToNormalDate(unixTimestamp) {
    // Convert Unix timestamp from seconds to milliseconds
    const date = new Date(unixTimestamp * 1000);
    
    // Get individual date components
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
    const day = String(date.getDate()).padStart(2, '0');
    
    // Get individual time components
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    
    // Format and return the date string in YYYY-MM-DD HH:MM format
    return `${year}-${month}-${day} ${hours}:${minutes}`;
}
#End If

Related Content

 
Top