Android Question How to return two parameters using a resumable sub?

dennmtr

Member
Licensed User
B4X:
[CODE=b4x]Public Sub MySub ' In Another Component
    Sleep(1000)
    CallSub3(Sender, "MySub_Complete", True, Null)
End Sub

Public Sub MySub2 ' In Current Component
    Dim SenderObject As Object = MySub
    Wait For (SenderObject) MySub_Complete (Success As Boolean, Result As Object)
    Log(Success)
End Sub

Public Sub MySub2
'Avoid MySub
'Avoid Wait For MySub_Complete(Success As Boolean, Result As Object)
Wait For (MySub) Complete (Success As Boolean, Result As Object)
End Sub[/CODE]

I want to avoid using callsub, but i dont know how to return more than one parameters as the example above!

Or something like this:

B4X:
    Dim SenderFilter As Object = SQL_.ExecQueryAsync("SQL", QueryString, Args)
    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, Rs As ResultSet)

How SQL_.ExecQueryAsync Returns the two values without using component value that callsub needs?

This doesnt work even not fails:

B4X:
Public Sub MySubInAnotherComponent
    Sleep(1000)
    CallSub3(Sender, "MySubInAnotherComponent_Complete", True, Null)
End Sub

Public Sub MyCurrentSub
    Dim SenderObject As Object = MySubInAnotherComponent
    Wait For (SenderObject) MySubInAnotherComponent_Complete (Success As Boolean, Result As Object)
    Log(Success)
End Sub
 
Last edited:

dennmtr

Member
Licensed User
As a ResumableSub (in contrast to an event that you WaitFor) can only return a single values then return a List or an Array containing the required values.
Yes but i dont like this approach :(, too messy
 
Upvote 0

Mahares

Expert
Licensed User
Longtime User
Someone asked the same question in this link.
 
Upvote 0

dennmtr

Member
Licensed User
Someone asked the same question in this link.

Nope, i will use the Component, EventName approach
Still SQL's lib approach is a mystery
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Still SQL's lib approach is a mystery
No it's not. As I said in post #2 you can can wait for events as well as Resumable Subs. Events can have more than one parameter and when you wait for an event your parameter list in the Wait For has to match that of the event. If you wait for an event then the actual event Sub, if present, will not be executed as the Wait For pre-empts the event and swallows it. SQL QueryComplete is an event. However as Subs can only return a single value when you Wait For a Resumable Sub you can only have a single parameter in the Wait For parameter list.
 
Upvote 0

dennmtr

Member
Licensed User
No it's not. As I said in post #2 you can can wait for events as well as Resumable Subs. Events can have more than one parameter and when you wait for an event your parameter list in the Wait For has to match that of the event. If you wait for an event then the actual event Sub, if present, will not be executed as the Wait For pre-empts the event and swallows it. SQL QueryComplete is an event. However as Subs can only return a single value when you Wait For a Resumable Sub you can only have a single parameter in the Wait For parameter list.
Corrent but this still doesnt solves the mystery on how SQL sub call back the event to the component without having the component passed as object. maybe is some java inside something like callsub(this, eventname + "")
 
Last edited:
Upvote 0

agraham

Expert
Licensed User
Longtime User
how SQL sub call back the event to the component without having the component passed as object.
Component is not the really the correct term, object instance is more correct. The event Sub name prefix, in this case 'SQL', indicates to the object instance which Sub it is to call for the event. Usually the event name is passed to an object instance when it is initialised but in the case of SQL it is passed to each ExecQueryAsync or ExecNonQueryBatch call as in your code fragment above
 
Upvote 0

josejad

Expert
Licensed User
Longtime User
I do it the way agraham says -and I would listen to what he says, not just as an expert, but as someone who has even been involved in some way in the initial development of B4X (correct me if I'm wrong) - and I don't think it's too messy. Just add the parameters to a map, and return it.
Or the way LucasMs says, just return a custom type, "Rs As ResultSet" is a custom type indeed

B4X:
Public Sub GetRecord (Command As String, parameters() As String) As ResumableSub
    Dim Answer As Map
    Answer.Initialize
    Dim req As DBRequestManager = CreateRequest
    Dim cmd As DBCommand = CreateCommand(Command, parameters)
    Wait For (req.ExecuteQuery(cmd, 0, Null)) JobDone(j As HttpJob)
    Answer.Put("status", j.Success)
    If j.Success Then
        req.HandleJobAsync(j, "req")
        Wait For (req) req_Result(Res As DBResult)
        'work with result
      ' req.PrintTable(Res)
        Answer.Put("Status","Data read succesfuly")
    Else
        Log("ERROR: " & j.ErrorMessage)
        Answer.Put("Status","Error getting data. Error: " & j.ErrorMessage)
    End If
    j.Release
    Answer.Put("Data",Res)

    Return Answer
End Sub
 
Upvote 0

dennmtr

Member
Licensed User
I do it the way agraham says -and I would listen to what he says, not just as an expert, but as someone who has even been involved in some way in the initial development of B4X (correct me if I'm wrong) - and I don't think it's too messy. Just add the parameters to a map, and return it.
Or the way LucasMs says, just return a custom type, "Rs As ResultSet" is a custom type indeed

B4X:
Public Sub GetRecord (Command As String, parameters() As String) As ResumableSub
    Dim Answer As Map
    Answer.Initialize
    Dim req As DBRequestManager = CreateRequest
    Dim cmd As DBCommand = CreateCommand(Command, parameters)
    Wait For (req.ExecuteQuery(cmd, 0, Null)) JobDone(j As HttpJob)
    Answer.Put("status", j.Success)
    If j.Success Then
        req.HandleJobAsync(j, "req")
        Wait For (req) req_Result(Res As DBResult)
        'work with result
      ' req.PrintTable(Res)
        Answer.Put("Status","Data read succesfuly")
    Else
        Log("ERROR: " & j.ErrorMessage)
        Answer.Put("Status","Error getting data. Error: " & j.ErrorMessage)
    End If
    j.Release
    Answer.Put("Data",Res)

    Return Answer
End Sub
Me finally:

ResponseBuilder.bas:
Sub Class_Globals
    Private Success_ As Boolean
    Private Data_ As Object
    Private Params_ As Map
End Sub

Public Sub Initialize

End Sub

Public Sub getSuccess As Boolean
    Return Success_
End Sub

Public Sub setSuccess(Success As Boolean)
    Success_ = Success
End Sub

Public Sub getFailed As Boolean
    Return Not(Success_)
End Sub

Public Sub getData As Object
    Return Data_
End Sub

Public Sub setData(Data As Object)
    Data_ = Data
End Sub

Public Sub getResultSet As ResultSet
    Return Data_
End Sub

Public Sub getAsByte As Byte
    Return Data_
End Sub

Public Sub getAsShort As Short
    Return Data_
End Sub

Public Sub getAsInt As Int
    Return Data_
End Sub

Public Sub getAsLong As Long
    Return Data_
End Sub

Public Sub getAsDouble As Double
    Return Data_
End Sub

Public Sub getAsFloat As Float
    Return Data_
End Sub

Public Sub getAsString As String
    Return Data_
End Sub

Public Sub getAsBinary As Byte()
    Return Data_
End Sub

Public Sub getAsArray As String()
    Return Data_
End Sub

Public Sub getAsArrayInt As Int()
    Return Data_
End Sub

Public Sub getAsList As List
    Return Data_
End Sub

Public Sub getAsMap As Map
    Return Data_
End Sub

Public Sub First As Object
    Dim Rs As ResultSet
    Rs = Data_
    Rs.NextRow
    Return Rs.GetString2(0)
End Sub

Public Sub Count As Long
    If Success_ Then
        Dim Rs As ResultSet
        Rs = Data_
        Return Rs.RowCount
    End If
    Return 0
End Sub

Public Sub Any As Boolean
    Dim Value As String = FirstOrDefault
    If Value = Null Then
        Return False
    End If
    Return Value.As(Long) > 0
End Sub

Public Sub FirstOrDefault As String
    If Success_ Then
        Dim Rs As ResultSet
        Rs = Data_
        If Rs.RowCount > 0 Then
            Rs.Position = 0
            Return Rs.GetString2(0)
        End If
    End If
    Return Null
End Sub

Public Sub List As List
    Dim List1 As List
    List1.Initialize
    If Success_ Then
        Dim Rs As ResultSet
        Rs = Data_
        If Rs.RowCount > 0 Then
            Rs.Position = 0
            Do Until Rs.Position > Rs.RowCount - 1
                List1.Add(Rs.GetString2(0))
                If Not(Rs.NextRow) Then
                    Rs.Position = 0
                    Exit
                End If
            Loop
        End If
    End If
    Return List1
End Sub

Public Sub Map As Map
    Dim Map1 As Map
    Map1.Initialize
    If Success_ Then
        Dim Rs As ResultSet
        Rs = Data_
        If Rs.RowCount > 0 Then
            Rs.Position = 0
            For i = 0 To Rs.ColumnCount - 1
                Map1.Put(Rs.GetColumnName(i), Rs.GetString2(i))
            Next
        End If
    End If
    Return Map1
End Sub

Public Sub ListMap As List
    Dim List1 As List
    List1.Initialize
    If Success_ Then
        Dim Rs As ResultSet
        Rs = Data_
        If Rs.RowCount > 0 Then
            Rs.Position = 0
            Do Until Rs.Position > Rs.RowCount - 1
                Dim Map1 As Map
                Map1.Initialize
                For i = 0 To Rs.ColumnCount - 1
                    Map1.Put(Rs.GetColumnName(i), Rs.GetString2(i))
                Next
                List1.Add(Map1)
                If Not(Rs.NextRow) Then
                    Rs.Position = 0
                    Exit
                End If
            Loop
        End If
    End If
    Return List1
End Sub

Public Sub GetParam(Key As Object) As Object
    If Not(Params_.IsInitialized) Then
        Return Null
    End If
    Return Params_.GetDefault(Key, Null)
End Sub

Public Sub SetParam(Key As Object, Value As Object)
    If Not(Params_.IsInitialized) Then
        Params_.Initialize
    End If
    Params_.Put(Key, Value)
End Sub

Response.bas:
'Code module
'Subs in this code module will be accessible from all modules.
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
End Sub

Public Sub Create(Success As Boolean, Data As Object) As ResponseBuilder
    Dim Response As ResponseBuilder
    Response.Initialize
    Response.Success = Success
    Response.Data = Data
    Return Response
End Sub


B4X:
Public Sub ExecQueryAsync2(QueryString As String, Args As List) As ResumableSub
    Dim SenderFilter As Object = SQL_.ExecQueryAsync("SQL", QueryString, Args)
    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, Rs As Object) 'Rs As ResultSet will throw an uncatchable exception if query fail
    Return Response.Create(Success, Rs)
End Sub
 
Last edited:
Upvote 0

dennmtr

Member
Licensed User
Why not inline the Create Sub code after Wait For and return the ResponseBuilder instance from the ExecQueryAsync2 ResumableSub?
Look the difference on those two examples:

UserRepository.bas:
Public Sub GetUsers3(UserId As Long, Columns() As String, PageNumber As Int, PageSize As Int) As Query
    Dim Users As Query
    Users.Initialize("users")
    Users = Users _
    .PrimaryKey("id") _
    .SelectColumns(Columns) _
    .Join("user_relations", Expression.Create2("user_relations", "user_id").Eq3("users", "id")) _
    .Where(Expression.Create2("user_relations", "user_profile_id").Eq(UserId)) _
    .Skip((PageNumber - 1) * PageSize) _
    .Take(PageSize)
    Return Users
End Sub

Public Sub GetUsers3(UserId As Long, Columns() As String, PageNumber As Int, PageSize As Int) As Query
    Dim Expression1 As ExpressionBuilder
    Expression1.Initialize
    Expression1.TableName = "user_relations"
    Expression1.ColumnName = "user_id"
    Expression1 = Expression1.Eq3("users", "id")
    Dim Expression2 As ExpressionBuilder
    Expression2.Initialize
    Expression2.TableName = "user_relations"
    Expression2.ColumnName = "user_profile_id"
    Expression2 = Expression2.Eq(UserId)
    Dim Users As Query
    Users.Initialize("users")
    Users = Users _
    .PrimaryKey("id") _
    .SelectColumns(Columns) _
    .Join("user_relations", Expression1) _
    .Where(Expression2) _
    .Skip((PageNumber - 1) * PageSize) _
    .Take(PageSize)
    Return Users
End Sub
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Look the difference on those two examples:
Sorry - but I have no idea what you mean by that. I meant

B4X:
Public Sub ExecQueryAsync2(QueryString As String, Args As List) As ResumableSub
    Dim SenderFilter As Object = SQL_.ExecQueryAsync("SQL", QueryString, Args)
    Wait For (SenderFilter) SQL_QueryComplete (Success As Boolean, Rs As ResultSet)    
    Dim Response As ResponseBuilder
    Response.Initialize
    Response.Success = Success
    Response.Data = Data.As(Object)
    Return Response
End Sub
I'm not familiar with ResponseBuilder but as you cast it to Object in Sub Create I've cast it to Object in the assignment to Response.Data. That might not be needed depending upon the type expected.
 
Upvote 0
Top