Android Question Passing Cursor in as a parameter creates an error

dieterp

Active Member
Licensed User
Longtime User
Since the latest update of B4A, there is a section of code relating to passing in a cursor as parameter that is no longer performing as it did in the past. The following line of code passes a cursor, map and spinner in as parameters to a "General Module" that then populates all the parameters and returns them to the original function (Note that "CursorScorerUmpire" is declared in Process_Globals):
B4X:
modGeneral.QueryScorerUmpire(CursorScorerUmpire, spnUmpire2, MapScorerUmpire, NewGame.TheServerRegionID, "Please select an Umpire")


The "General Module" code header is as follows:
B4X:
Sub QueryScorerUmpire(CursorScorerUmpire As Cursor, SpnScorerUmpire As Spinner, mapScorerUmpire As Map, RegionID As Int, HintText As String)


Once the Cursor, Spinner and Map are populated by running a DB query in the code above, the objects are passed back to the original Sub. At that point I run the following code to check if the Cursor Recordset does contain any information:
B4X:
If CursorScorerUmpire.RowCount = 0 Then


It's at this point that I receive an error stating that the "CursorScorerUmpire" cursor has not been initialized (There is definitely data in the Recordset when it passes through the "General Module"). In the past there would be no such issue. Is it now not possible to pass in Cursor's as a parameter, and what is the underlying reason for this?
 

DonManfred

Expert
Licensed User
Longtime User
A Cursor is not a Resultset.

Where does the Cursor comes from? I don´t see any related code in your Post.
 
Upvote 0

dieterp

Active Member
Licensed User
Longtime User
I have an Activity Module called "Officials". Under the Process_Globals section of "Officials" I declare the cursor as follows:
B4X:
Sub Process_Globals
    Dim CursorScorerUmpire As Cursor
End Sub

In the "Activity_Resume" Sub of the "Officials" module I then run the following code line (Amongst others):
B4X:
modGeneral.QueryScorerUmpire(CursorScorerUmpire, spnUmpire2, MapScorerUmpire, NewGame.TheServerRegionID, "Please select an Umpire")

I have a Module called "modGeneral" that handles reusable queries across the app. One of the subs in modGeneral is called "QueryScorerUmpire". This is the sub that will populate the Cursor, Spinner and Map that gets passed in from the "Officials" Module. Here is the full code for that Sub
B4X:
Sub QueryScorerUmpire(CursorScorerUmpire As Cursor, SpnScorerUmpire As Spinner, mapScorerUmpire As Map, RegionID As Int, HintText As String)

'Dim CursorTeams As
    
    CursorScorerUmpire = Main.SQL1.ExecQuery("Select UserID, Surname || ', ' || Name as FullName From ScorerUmpire Where RoleID in (6,7) And RegionID = " & RegionID & " Order By Surname, Name COLLATE NOCASE, Name COLLATE NOCASE")

    SpnScorerUmpire.Clear
    mapScorerUmpire.Clear

    SpnScorerUmpire.Add(HintText)
    mapScorerUmpire.Put(HintText, 0)

    If CursorScorerUmpire.RowCount <> 0 Then

        For I = 0 To CursorScorerUmpire.RowCount - 1
        
            CursorScorerUmpire.Position = I
            
            SpnScorerUmpire.Add(CursorScorerUmpire.GetString("FullName"))
            mapScorerUmpire.Put(CursorScorerUmpire.GetString("FullName"), CursorScorerUmpire.GetInt("UserID"))
            
        Next

        CursorScorerUmpire.Close
    
    End If

End Sub
B4X:

The reason I do this is so that the Cursor, Spinner and Map all get updated through one process call. When I step through the code I see all the objects getting populated, but when the code returns to the "Officials" module and I check to see if the "CursorScorerUmpire" has indeed been populated, I get the error that the "CursorScorerUmpire" has not been Initalized.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Assuming that this has worked unchanged previously I think this is an object wrapper problem possibly caused by a change that Erel made in v9.90
Fix for a design issue that existed since B4A v1.0 where in some cases assignment of an object to a variable can also change other variables that point to the same "wrapper".
I suspect that
B4X:
    CursorScorerUmpire = Main.SQL1.ExecQuery(...)
is generating a new Cursorwrapper object with the Java Cursor object returned from ExecQuery. You could try

B4X:
Dim TempCursor as Cursor
TempCursor = Main.SQL1.ExecQuery(...)
CursorScorerUmpire = TempCursor
This might won't work but is obviously unintuitive. I await Erel's greater insight.

EDIT: Corrected a mis-assumption.
 
Last edited:
Upvote 0

Mahares

Expert
Licensed User
Longtime User
the "CursorScorerUmpire" has not been Initalized.
You declare the cursor in Process_Globals in the 'Officials' activity module, but then you override that declaration in the Sub:
Sub QueryScorerUmpire(CursorScorerUmpire As Cursor which cancels out the first declaration.
Try to declare it once in the Process_Globals and remove its declaration from: Sub QueryScorerUmpire(CursorScorerUmpire As Cursor.
If you want to still have access to the cursor 's data, do not close it until you no longer need its data. Does this make sense.
 
Upvote 0

dieterp

Active Member
Licensed User
Longtime User
Thanks for the above feedback. I have found a workaround by replacing the code that checks that the cursor doesn't return an empty Recordset with the following code. It will check that the Spinner isn't empty rather than the Cursor:
B4X:
'If CursorScorerUmpire.RowCount = 0 Then
If spnHomeScorer.Size <= 1 Then

I'm happy to use the workaround in all code aspects moving forward, but I just felt it best to raise this "issue" as I'm not sure if it is a bug (As this worked before), or if this is meant to be the case now. We can see if Erel has any particular feedback, but I'll continue with the workaround for now
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
The code snippets are a bit confusing as the names are the same, but the sub is in a different module.

If you want to update Main.CursorScorerUmpire then the code should be:
B4X:
Sub QueryScorerUmpire(SpnScorerUmpire As Spinner, mapScorerUmpire As Map, RegionID As Int, HintText As String)
Main.CursorScorerUmpire = Main.SQL1.ExecQuery("Select UserID, Surname ...

The change is related to the fix @agraham mentioned. Assigning a value to a local variable (and it is a local variable) should never affect other variables.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
My code fragment above is wrong and will not work as I misunderstood something (how unusual!)
Assigning a value to a local variable (and it is a local variable) should never affect other variables
<pedantry>This might be misunderstood on a superficial reading. Assigning a value to a property of a parameter (local) variable that contains a reference type will still work. It is the assignment of a new instance of that reference type to a local variable that will not now affect the originally passed variable. i.e. the normal rules for reference types now apply whereas they did not before in all situations. </pedantry> :)

<geeky>B4X reference types are actually need double de-referencing under the hood as they wrap the real reference object as a field in the type instance. i.e the type instances are built by composition, not inheritance. To appear like normal reference types the B4X compiler needs to do some extra trickery in certain circumstance by reassigning the underlying real object from one wrapper to the other. The change I mentioned corrects the behaviour in this circumstance. Before it (incorrectly) reassigned the underlying object of the passed wrapper to the new real object, now it doesn't, it just assigns the the new wrapper type instance to the local parameter variable. </geeky>
 
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
B4X reference types are actually need double referencing under the hood as they wrap the real reference object as a field in the type instance. i.e the type instances are built by composition, not inheritance. To appear like normal reference types the B4X compiler needs to do some extra trickery in certain circumstance by reassigning the underlying real object from one wrapper to the other. The change I mentioned corrects the behaviour in this circumstance. Before it (incorrectly) reassigned the underlying object of the passed wrapper to the new real object, now it doesn't, it just assigns the the new wrapper type instance to the local parameter variable.
Not pedantry enough. It is only true for some types which are implemented as wrappers. The main purpose of this feature is to make it possible to create wrappers that go together with the native types inheritance model. The wrapper of button inherits from the wrapper of label which inherits from the wrapper of view.
So in this case it is actually a combination of composition and inheritance.

This should be transparent to the developer and the fix in the last version actually makes it transparent.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
So in this case it is actually a combination of composition and inheritance.
I was trying to keep it simple (!) - you don't need to understand wrapper inheritance but it helps on occasion to know that a wrapper type wraps the object and so needs a (hopefully totally transparent) double de-reference under the hood (bonnet for us Brits who speak 'proper' English ;) ).
 
Upvote 0
Top