B4J Question (solved) Resumable Subs with Callback from another Class

aeric

Expert
Licensed User
Longtime User
I have 2 classes, FirebaseAuth and GoogleSheetsAPI.

In B4XMainPage, I initialized the objects and call the methods.
B4XMainPage:
Sub Class_Globals
    Private api As GoogleSheetsAPI
    Private auth As FirebaseAuth
End Sub

Public Sub Initialize
    'B4XPages.GetManager.LogEvents = True
    api.Initialize(Me, "SheetManager")
    auth.Initialize(Me, "Auth")
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
  
    auth.SignInAnonymously
End Sub

In FirebaseAuth:
FirebaseAuth:
Sub Class_Globals
    Private EventName As String
    Private Callback As Object
End Sub

Public Sub Initialize (CallbackModule As Object, EventNameStr As String)
    Callback = CallbackModule
    EventName = EventNameStr

    tmrRefresh.Initialize("tmrRefresh", 300000) '5 minutes
End Sub

'Resumable sub
Public Sub SignInAnonymously
    Dim job As HttpJob
    job.Initialize("", Me)
    job.PostString(AUTH_BASE & FIREBASE_API_KEY, body)
    job.GetRequest.SetHeader("Content-Type", "application/json")
    Wait For (job) JobDone(job As HttpJob)
    If job.Success Then
        Dim response As String = job.GetString
        ' the rest of code
      
        CallSubDelayed3(Callback, EventName & "_AuthComplete", True, "")
        CallSubDelayed3(Callback, EventName & "_TokenResult", token, "")
    Else
        CallSubDelayed3(Callback, EventName & "_AuthComplete", False, msg)
        CallSubDelayed3(Callback, EventName & "_TokenResult", "", msg) 
    End If
    job.Release
End Sub

In GoogleSheetAPI:
GoogleSheetAPI:
Sub Class_Globals
    Private EventName As String
    Private Callback As Object

    #If B4J
    Private BaseURL As String = "http://127.0.0.1:8080/api"
    #Else
    Private BaseURL As String = "https://your-backend.com/api"
    #End If

    Private AuthToken As String
End Sub

Public Sub Initialize (CallbackModule As Object, EventNameStr As String)
    Callback = CallbackModule
    EventName = EventNameStr
End Sub

Public Sub LoadData (SheetID As String)
    Dim url As String = BaseURL & "/sheets/" & SheetID
    SendRequest("LOAD", url, "")
End Sub

Public Sub AddRecord (SheetID As String, Name As String, Email As String, Phone As String)
    Dim url As String = BaseURL & "/sheets/" & SheetID
    Dim json As String = CreateJSON(Name, Email, Phone)
    SendRequest("ADD", url, json)
End Sub

Private Sub SendRequest (Tag As String, Url As String, PostData As String)
    Log($"SendRequest(${Tag}, ${Url}, ${PostData})"$)
    If TokenEmpty Then Return

    Dim job As HttpJob
    job.Initialize(Tag, Me)

    Select Tag
        Case "LOAD"
            job.Download(Url)
        Case "ADD", "UPDATE", "CREATE"
            job.PostString(Url, PostData)
        Case "DELETE"
            job.PostString(Url, "")
            Dim req As JavaObject = job.GetRequest
            req.RunMethod("setRequestMethod", Array("DELETE"))
    End Select

    job.GetRequest.SetHeader("Authorization", "Bearer " & AuthToken)
    job.GetRequest.SetHeader("Content-Type", "application/json")

    Log("Request: " & Tag & " -> " & Url)
    Wait For (job) JobDone(job As HttpJob)
    ParseResponse(job, Tag)
    job.Release
End Sub

Private Sub ParseResponse (Job As HttpJob, Tag As String)
    If Job.Success Then
        Dim response As String = Job.GetString
        Log($"(${Tag}) Response: ${response}"$)
        Try
            Dim root As Map = response.As(JSON).ToMap
            Dim success As Boolean = root.GetDefault("success", False)
            Dim errorMsg As String = root.GetDefault("error", "")

            If success Then
                Select Tag
                    Case "LOAD"
                        Dim data As List
                        If root.ContainsKey("data") Then
                            Dim rawData As List = root.Get("data")
                            data.Initialize
                            For Each item As Map In rawData
                                Dim row As Map
                                row.Initialize
                                row.Put("ID", item.GetDefault("id", ""))
                                row.Put("Name", item.GetDefault("name", ""))
                                row.Put("Email", item.GetDefault("email", ""))
                                row.Put("Phone", item.GetDefault("phone", ""))
                                data.Add(row)
                            Next
                        Else
                            data.Initialize
                        End If
                        Dim m As Map
                        m.Initialize
                        m.Put("success", True)
                        m.Put("data", data)
                        m.Put("error", "")
                        CallSubDelayed2(Callback, EventName & "_DataLoaded", m)

                    Case "ADD"
                        Dim record As Map
                        If root.ContainsKey("data") Then
                            record = root.Get("data")
                        Else
                            record.Initialize
                        End If
                        Dim m As Map
                        m.Initialize
                        m.Put("success", True)
                        m.Put("record", record)
                        m.Put("error", "")
                        CallSubDelayed2(Callback, EventName & "_RecordAdded", m)

                    Case "UPDATE"
                        CallSubDelayed3(Callback, EventName & "_RecordUpdated", True, "")

                    Case "DELETE"
                        CallSubDelayed3(Callback, EventName & "_RecordDeleted", True, "")

                    Case "CREATE"
                        Dim sheetID As String = root.GetDefault("sheetId", "")
                        Dim m As Map
                        m.Initialize
                        m.Put("success", True)
                        m.Put("sheetId", sheetID)
                        m.Put("error", "")
                        CallSubDelayed2(Callback, EventName & "_SheetCreated", m)
                End Select
            Else
                Select Tag
                    Case "LOAD"
                        Dim m As Map
                        m.Initialize
                        m.Put("success", False)
                        m.Put("data", Null)
                        m.Put("error", errorMsg)
                        CallSubDelayed2(Callback, EventName & "_DataLoaded", m)
                    Case "ADD"
                        Dim m As Map
                        m.Initialize
                        m.Put("success", False)
                        m.Put("record", Null)
                        m.Put("error", errorMsg)
                        CallSubDelayed2(Callback, EventName & "_RecordAdded", m)
                    Case "UPDATE"
                        CallSubDelayed3(Callback, EventName & "_RecordUpdated", False, errorMsg)
                    Case "DELETE"
                        CallSubDelayed3(Callback, EventName & "_RecordDeleted", False, errorMsg)
                    Case "CREATE"
                        Dim m As Map
                        m.Initialize
                        m.Put("success", False)
                        m.Put("sheetId", "")
                        m.Put("error", errorMsg)
                        CallSubDelayed2(Callback, EventName & "_SheetCreated", m)
                End Select
            End If
        Catch
            Log("Parse error: " & LastException.Message)
            Select Tag
                Case "LOAD"
                    Dim m As Map
                    m.Initialize
                    m.Put("success", False)
                    m.Put("data", Null)
                    m.Put("error", "Invalid server response")
                    CallSubDelayed2(Callback, EventName & "_DataLoaded", m)
                Case "ADD"
                    Dim m As Map
                    m.Initialize
                    m.Put("success", False)
                    m.Put("record", Null)
                    m.Put("error", "Invalid server response")
                    CallSubDelayed2(Callback, EventName & "_RecordAdded", m)
                Case "UPDATE"
                    CallSubDelayed3(Callback, EventName & "_RecordUpdated", False, "Invalid server response")
                Case "DELETE"
                    CallSubDelayed3(Callback, EventName & "_RecordDeleted", False, "Invalid server response")
                Case "CREATE"
                    Dim m As Map
                    m.Initialize
                    m.Put("success", False)
                    m.Put("sheetId", "")
                    m.Put("error", "Invalid server response")
                    CallSubDelayed2(Callback, EventName & "_SheetCreated", m)
            End Select
        End Try
    Else
        LogColor($"(${Tag}) HTTP error: ${Job.ErrorMessage}"$, 0xFFFF0000)
        Select Tag
            Case "LOAD"
                Dim m As Map
                m.Initialize
                m.Put("success", False)
                m.Put("data", Null)
                m.Put("error", "Network error: " & Job.ErrorMessage)
                CallSubDelayed2(Callback, EventName & "_DataLoaded", m)
            Case "ADD"
                Dim m As Map
                m.Initialize
                m.Put("success", False)
                m.Put("record", Null)
                m.Put("error", "Network error: " & Job.ErrorMessage)
                CallSubDelayed2(Callback, EventName & "_RecordAdded", m)
            Case "UPDATE"
                CallSubDelayed3(Callback, EventName & "_RecordUpdated", False, "Network error: " & Job.ErrorMessage)
            Case "DELETE"
                CallSubDelayed3(Callback, EventName & "_RecordDeleted", False, "Network error: " & Job.ErrorMessage)
            Case "CREATE"
                Dim m As Map
                m.Initialize
                m.Put("success", False)
                m.Put("sheetId", "")
                m.Put("error", "Network error: " & Job.ErrorMessage)
                CallSubDelayed2(Callback, EventName & "_SheetCreated", m)
        End Select
    End If
End Sub

B4XMainPage:
Private Sub ConnectToSheet (SheetID As String)
    Log($"ConnectToSheet(${SheetID})"$)
    If IsLoading Then Return
    IsLoading = True
    SetControlsEnabled(False)
    ShowStatus("Connecting...", xui.Color_ARGB(255, 66, 133, 244))
    api.LoadData(SheetID)
End Sub

'--- Callbacks from GoogleSheetsAPI ---

Private Sub SheetManager_DataLoaded (m As Map)
    IsLoading = False
    SetControlsEnabled(True)
    Dim Success As Boolean = m.GetDefault("success", False)
    Dim Data As List = m.GetDefault("data", Null)
    Dim ErrorMsg As String = m.GetDefault("error", "")
    If Success Then
        IsConnected = True
        txtSheetID.Text = CurrentSheetID
        B4XTable1.SetData(Data)
        ShowStatus("Loaded " & Data.Size & " records", xui.Color_ARGB(255, 52, 168, 83))
    Else
        IsConnected = False
        ShowStatus("Error: " & ErrorMsg, xui.Color_ARGB(255, 234, 67, 53))
    End If
End Sub

'--- Auth callbacks ---

Private Sub Auth_AuthComplete (Success As Boolean, ErrorMessage As String)
    Log($"Auth_AuthComplete(${Success}, ${ErrorMessage})"$)
    If Success Then
        ShowStatus("Authenticated. Ready to connect.", xui.Color_ARGB(255, 52, 168, 83))
        CallSubDelayed(Me, "DelayedConnect")
    Else
        ShowStatus("Auth failed: " & ErrorMessage, xui.Color_ARGB(255, 234, 67, 53))
    End If
End Sub

Private Sub DelayedConnect
    Dim savedID As String = LoadSavedSheetID
    Log(savedID)
    If savedID <> "" Then
        txtSheetID.Text = savedID
        CurrentSheetID = savedID
        ConnectToSheet(savedID)
    End If
End Sub

Private Sub Auth_TokenResult (Token As String, ErrorMessage As String)
    Log($"Auth_TokenResult(${Token}, ${ErrorMessage})"$)
    If Token <> "" Then
        Log("Setting AuthToken")
        api.SetAuthToken(Token)
    Else
        Log("Token is empty: " & ErrorMessage)
    End If
End Sub

I think I am facing a race condition.
I am not familiar with such callback calling so I am seeking help to understand the prolem.
 
Last edited:

aeric

Expert
Licensed User
Longtime User
The problem now is the response returned from the server is empty.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
I found the issue.
It is not at client side but server was calling a Wait For in Handle sub.
I solved it by moving out the code to another sub.

Instead of writing:
B4X:
Public Sub Handle (req As ServletRequest, resp As ServletResponse)

    Wait For (ValidateToken(idToken)) Complete (validationResult As Map)

End Sub

I need to write:
B4X:
Public Sub Handle (req As ServletRequest, resp As ServletResponse)

    AuthenticateUser(idToken)

End Sub

Private Sub AuthenticateUser (idToken As String)
    Wait For (ValidateToken(idToken)) Complete (validationResult As Map)
    If validationResult.Get("valid") = False Then
        Log("Invalid Token")
        Dim errMsg As String = validationResult.GetDefault("error", "Invalid Token")
        SendError(401, errMsg)
        Return
    End If
    Dim userId As String = validationResult.Get("uid")
    Log("Authenticated User: " & userId)
    HandleRoutes
End Sub

 
Last edited:
Upvote 0
Top