B4J Question how to retrieve an object stored in a map

HowardTheDuck

Member
Licensed User
Hello, can someone point out what I'm doing wrong here? I'm new to this language and OOP.

I'm creating objects of type "Something" and storing them in a map "MyMap"
Later I iterate over this map and extract an object on each iteration, assigning the object to "Entity"
I then attempt to run a valid method on this object, such as Entity.ReportData

At this point, it fails and generates an error in the logs; "Error occurred on line: 43 (Main)
java.lang.NullPointerException"

What am I doing wrong?

Main
B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private lblTest As Label
    Public MyMap As Map
    
    Public Timer1 As Timer
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Main") 'Load the layout file.
    MainForm.Show
    Timer1.Initialize("Timer1",1000)
    Timer1.Enabled = True
    MyMap.Initialize
    Begin
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Public Sub Begin
    For i = 0 To 12
        Dim Entity As Something
        Entity.Initialize(i,i,i)
        MyMap.Put(i,Entity)
    Next
End Sub

Public Sub Timer1_Tick
    For Each k As String In MyMap.Keys
        Log(k)
        Dim Entity As Something
        Entity = MyMap.Get(k)
        Log(Entity.ReportData)
    Next
End Sub

Class Something
B4X:
Sub Class_Globals
    Private fx As JFX
    Private Data1, Data2 As String
    Private ID As Int
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(x_Data1 As String, x_Data2 As String, x_ID As Int)
    Data1 = x_Data1
    Data2 = x_Data2
    ID = x_ID   
End Sub

Public Sub ReportData As String
    Return Data1 & "x" & Data2
End Sub

Public Sub ReportID As Int
    Return ID
End Sub

Error
B4X:
Waiting for debugger to connect...
Program started.
0
Error occurred on line: 43 (Main)
java.lang.NullPointerException
    at b4j.test.main._timer1_tick(main.java:165)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.objects.Timer$TickTack$1.run(Timer.java:118)
    at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$3(WinApplication.java:177)
    at java.lang.Thread.run(Thread.java:748)
 

jmon

Well-Known Member
Licensed User
Longtime User
Hi,

It's because of the way you retrieve the values in the timer loop.

The "keys" of your map are first inserted as "int", but later, you loop through the keys as "string". The map doesn't convert int to string when it does it's look up. i.e. That's why it can't find the key "1" (as string), but could have found 1 (as int).

So it's better to change your example like this:
B4X:
Public Sub Timer1_Tick
    For Each k As Int In MyMap.Keys
        Log(k)
        Dim Entity As Something
        Entity = MyMap.Get(k)
        Log(Entity.ReportData)
    Next
End Sub
or
B4X:
Public Sub Timer1_Tick
    For k = 0 To MyMap.Size -1
        Log(k)
        Dim Entity As Something
        Entity = MyMap.Get(k)
        Log(Entity.ReportData)
    Next
End Sub
or this way, which is the safest:
B4X:
Public Sub Timer1_Tick
    For i = 0 To MyMap.Size -1
        Dim k As Int = MyMap.GetKeyAt(i)
        Dim Entity As Something
        Entity = MyMap.GetValueAt(k)
        Log(Entity.ReportData)
    Next
End Sub
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Hi,


or this way, which is the safest:

B4X:
Public Sub Timer1_Tick
    For i = 0 To MyMap.Size -1
        Dim k As Int = MyMap.GetKeyAt(i)
        Dim Entity As Something
        Entity = MyMap.GetValueAt(k)
        Log(Entity.ReportData)
    Next
End Sub

I don't think Erel would agree with using GetKeyAt and GetValueAt (slow, inefficient and not supported in iOS).

For Each k As Int In MyMap.Keys
is now the preferred and recommended method (B4X standard).

Thanks
 
Upvote 0
Top