Android Question Cursor/ResultSet object still initialized after .close()

Hello everyone,

I'm working with a Cursor/ResultSet object. After I finish all the necessary operations, I call the .close() method on it, as is standard practice to release resources.

However, I've noticed that even after calling .close(), the variable holding the Cursor/ResultSet object is not null. If I inspect it, it still appears to be an initialized object and even retains some property values, like the RowCount > 0.

My understanding is that to fully release the object and ensure it's garbage collected, I should not only call .close() but also explicitly set the variable to null (e.g., myCursor = null). When I do this, the variable correctly reflects a non-initialized state.

My question is: Is this behavior by design? We need to execute both operation .close() and set = null? Can you suggest a correct use of Cursor/ResultSet object?

I've attached a small test project to this post that clearly demonstrates the behavior.

my development environment:
  • B4A Version: 13.40
  • android.jar: 36
  • Build Tools: 36.0.0

Thanks in advance for any insights!
 

Attachments

  • SqlCloseTest.zip
    14.5 KB · Views: 19

stevel05

Expert
Licensed User
Longtime User
The Cursor object should not be a global variable. It should be defined in the sub in which it is created and closed before it exits. That way, it is made available to be garbage collected as there is no pointers left to the object. In your example, you should Dim the cursor at the top of Logtable1 and call that when needed.

As it stands, if data is added or removed, it won't be reflected in the cursor contents if you log it from the Button1_Click sub, you would need to call the query again. If you need to persist the data, you should copy it to a different structure, a Map or list of Types, whichever is appropriate. There are exceptions to every rule, but this is the safest way to do it.
 
Upvote 0
Thanks for your reply.
To follow a best practice, do you recommend creating a class and/or methods that allow me to execute a query and return a class or a list of that class with values so I can manipulate them more effectively?

For a quick solution like the example, could I fix it by setting the cursor to null? Obviously, knowing the risk of object reflection?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
creating a class and/or methods
It really depends on the size and complexity of the project, but yes I usually create a class for updates and a class for queries that returns the data in a structure that is easy to handle in the app, just to keep them separate when it gets large.

could I fix it by setting the cursor to null
I would be simpler just to change the two subs:
B4X:
Private Sub Button1_Click
        LogTable1
End Sub

Sub LogTable1
    Dim Cursor1 As Cursor
    Cursor1 = SQL1.ExecQuery("SELECT col1, col2, col3 FROM table1")
    For i = 0 To Cursor1.RowCount - 1
        Cursor1.Position = i
        Log("************************")
        Log(Cursor1.GetString("col1"))
        Log(Cursor1.GetInt("col2"))
        Log(Cursor1.GetInt("col3"))
    Next
    Cursor1.Close
End Sub

and remove the Global Cursor1 definition. Then you know you are always getting the latest data.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
1. Use ResultSet instead of Cursor.

2. An object never becomes Null by itself and you shouldn't worry that it is still initialized. As @stevel05 wrote, it should be a local variable and its allocated memory will be released once the sub ends and there are no live references to that object.
Don't worry about memory leaks. They are very rare in B4X. You will need to do something like collect all ResultSets in a global List in order to create a memory leak.
 
Upvote 0
Ok, I'll use ResultSet. But is it possible to implement a Dispose that allows you to close it and set it to null? Is this so that isInitialized returns false as if it had never been initialized?
 
Upvote 0
Apologies if my previous point wasn't clear. My intention for using Dispose was not just to release memory, but also to effectively mark the object as 'uninitialized'. This would provide a reliable way to check if the cursor is ready for use. I envisioned this being particularly helpful in large-scale projects where refactoring every cursor instance would be a significant effort. It would allow us to close a cursor and later verify its initialization state before attempting another read operation. That said, if this approach goes against best practices, which state that a cursor's lifetime should be confined to a single method, I am prepared to undertake the necessary refactoring.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
As a general rule - avoid global variables whenever possible.

There are however valid reasons to use global variables and for this use case, you can make a ResultSet become uninitialized in two ways:
1. Calling dim again:
B4X:
Dim GlobalRs As ResultSet
'Now GlobalRs.IsInitialized = True

2. Assigning an uninitialized object:
B4X:
GlobalRs = EmptyRs

EmptyRs is declared as a global variable and is never initialized or assigned. You can reuse it as many times as needed.
I prefer this method.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
As a general rule - avoid global variables whenever possible.

There are however valid reasons to use global variables and for this use case, you can make a ResultSet become uninitialized in two ways:
1. Calling dim again:
B4X:
Dim GlobalRs As ResultSet
'Now GlobalRs.IsInitialized = True

2. Assigning an uninitialized object:
B4X:
GlobalRs = EmptyRs

EmptyRs is declared as a global variable and is never initialized or assigned. You can reuse it as many times as needed.
I prefer this method.
I suppose you meant to write the opposite:
B4X:
EmptyRs = GlobalRs
GlobalRs is never initialized.

It doesn't change anything but the names are more appropriate.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
No, the question here is about making a global object uninitialized again. One solution is to make another global object (EmptyRs) that is assigned to the "main" variable when you want to make it uninitialized.
Yes, I reread it and it's correct.
However, since Dim in B4X can also function as ReDim, and this could be a problem, then you might as well take advantage of this and use your first option 😁
[No, the second one is more "readable"]
 
Upvote 0
Top