Android Question [SOLVED] why does getActivityBA throw nullpointerexception

Lee Gillie CCP

Active Member
Licensed User
Longtime User
This is pretty odd. I have a code module named "Globals". I use routines in it from several class objects. In fact one class object had already used it successfully, but when a second class object calls the same routine from it, I get the exception. The stack dump is:
B4X:
Error occurred on line: 286 (OpenOrderLine)
java.lang.NullPointerException
    at anywheresoftware.b4a.B4AClass$ImplB4AClass.getActivityBA(B4AClass.java:20)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:636)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:302)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:238)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:121)
    at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:882)
    at anywheresoftware.b4a.keywords.Common.CallSubNew(Common.java:831)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:636)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:305)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:238)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:121)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:78)
    at android.view.View.performClick(View.java:4633)
    at android.view.View$PerformClick.run(View.java:19270)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5476)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
    at dalvik.system.NativeStart.main(Native Method)

In the case it works the debug call stack looks like
openorderheader.ToJSON - class instance
Globals.JSONValue - code module
serializableopenorder.ToJSON - class instance
Globals.JSONValue - code module
remotemethodcall.ToJSON - class instance
webapi.UploadOrders - class instance
main.btnCheckData - main activity

In the case it crashes the debug call stack (prior to crash) looks like
openorderline.ToJSON - class instance
Globals.JSONValue - code module
serializableopenorder.ToJSON - class instance
Globals.JSONValue - code module
remotemethodcall.ToJSON - class instance
webapi.UploadOrders - class instance
main.btnCheckData - main activity

Side note: Basically remotemethodcall, serializableopenorder, openorderheader, and openorderline are all classes, all implement a ToJSON method, each which call Globals.JSONValue. Globals.JSONValue may then obtain a class instance of a member and then call that classes ToJSON routine. Data is structured as a remotemethodcall has a member containing a list (call parameters), and that list contains an item holding a list of serializableopenorder. A serializableopenorder has a member containing a single openorderheader, and a list of openorderline. The total context is serializing the remotemethodcall instance, and therefore the recursion you see.

The ToJSON call in openorderline begins as
B4X:
Public Sub ToJSON As String
    Dim mp As Map
    mp.Initialize

    mp.Put("OrderNumber",Globals.JSONValue(mOrderNumber))
    mp.Put("LineNumber",Globals.JSONValue(mLineNumber))
    mp.Put("ProductNumber",Globals.JSONValue(mProductNumber))
    mp.Put("Description",Globals.JSONValue(mDescription))
    mp.Put("QOO",Globals.JSONValue(mQOO))
    mp.Put("QShip",Globals.JSONValue(mQShip))
    mp.Put("QDelivered",Globals.JSONValue(mQDelivered))

The failing line is
mp.Put("OrderNumber",Globals.JSONValue(mOrderNumber))

Try to step into this line and it stack dumps.

The running context is not that much different than when it ran similar code as the case that worked. In fact, it is within the very same call to serializableopenorder.ToJSON. That is what makes this so puzzling. So in concept this should work because the openorderheader.ToJSON works.

It consistently fails at the same place. I am mystified as to why this quit working for me. Scrambling to come up with an approach to diagnose this. Any ideas would be greatly appreciated.
 
Last edited:

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Here is java for the two classes (i.e. works and doesn't work). I don't see how "_globals" is set/initialized in any of this java code. openorderheader works, openorderline crashes.

B4X:
openorderheader.java

    public odp.eljaydelivery.globals _globals = null;
    ...
    public String  _tojson(odp.eljaydelivery.openorderheader __ref) throws Exception{
    __ref = this;
    RDebugUtils.currentModule="openorderheader";
    anywheresoftware.b4a.objects.collections.Map _mp = null;
    String _jsontext = "";
    RDebugUtils.currentLine=10878976;
     //BA.debugLineNum = 10878976;BA.debugLine="Public Sub ToJSON As String";
    RDebugUtils.currentLine=10878977;
     //BA.debugLineNum = 10878977;BA.debugLine="Dim mp As Map";
    _mp = new anywheresoftware.b4a.objects.collections.Map();
    RDebugUtils.currentLine=10878978;
     //BA.debugLineNum = 10878978;BA.debugLine="mp.Initialize";
    _mp.Initialize();
    RDebugUtils.currentLine=10878980;
     //BA.debugLineNum = 10878980;BA.debugLine="mp.Put(\"OrderNumber\",Globals.JSONValue(mOrderNumb";
    _mp.Put((Object)("OrderNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mordernumber))));
    RDebugUtils.currentLine=10878981;
     //BA.debugLineNum = 10878981;BA.debugLine="mp.Put(\"CustomerNumber\",Globals.JSONValue(mCustom";
    _mp.Put((Object)("CustomerNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mcustomernumber))));
    RDebugUtils.currentLine=10878982;

B4X:
[B]openorderline.java[/B]

    public odp.eljaydelivery.globals _globals = null;
    ...
    public String  _tojson(odp.eljaydelivery.openorderline __ref) throws Exception{
    __ref = this;
    RDebugUtils.currentModule="openorderline";
    anywheresoftware.b4a.objects.collections.Map _mp = null;
    String _jsontext = "";
    RDebugUtils.currentLine=13434880;
     //BA.debugLineNum = 13434880;BA.debugLine="Public Sub ToJSON As String";
    RDebugUtils.currentLine=13434881;
     //BA.debugLineNum = 13434881;BA.debugLine="Dim mp As Map";
    _mp = new anywheresoftware.b4a.objects.collections.Map();
    RDebugUtils.currentLine=13434882;
     //BA.debugLineNum = 13434882;BA.debugLine="mp.Initialize";
    _mp.Initialize();
    RDebugUtils.currentLine=13434884;
     //BA.debugLineNum = 13434884;BA.debugLine="mp.Put(\"OrderNumber\",Globals.JSONValue(mOrderNumb";
    _mp.Put((Object)("OrderNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mordernumber))));
    RDebugUtils.currentLine=13434885;
     //BA.debugLineNum = 13434885;BA.debugLine="mp.Put(\"LineNumber\",Globals.JSONValue(mLineNumber";
    _mp.Put((Object)("LineNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mlinenumber))));
    RDebugUtils.currentLine=13434886;
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Rocefer... thanks again for taking time to assist...

mOrderNumber is declared in openorderline.Class_Globas as Int.

Globals.JSONValue() is a work in progress, but thus far it looks like this:
B4X:
Public Sub JSONValue( obj As Object ) As String
    If obj = Null Then
        Return "null"
    End If
   
    Dim rfl As Reflector
    rfl.Target = obj
    Dim TypeName As String = rfl.TypeName
    Dim Dimensions As Int = 0
    Do While TypeName.StartsWith("[")
        Dimensions = Dimensions + 1
        TypeName = TypeName.SubString(1)
    Loop
    If Dimensions > 0 Then

        Dim TypeCode As String = TypeName.SubString2(0,1)
        TypeName = TypeName.SubString(1)

        If TypeCode = "L" Then
            If TypeName.EndsWith(";") Then TypeName = TypeName.SubString2(0,TypeName.Length-1)
        End If

        Dim sb As StringBuilder
        sb.Initialize
       
        sb.Append("[ ")
        Dim x() As Object
        x = obj
                   
        For i = 0 To x.Length-1
            If i <> 0 Then sb.Append(", ")
            Dim indices( Dimensions ) As Int
            indices(0) = i
            Dim itm As Object = rfl.GetArray(indices)
            Select Case TypeCode
                Case "B"
                    sb.Append( itm )
                Case "S"
                    sb.Append( itm )
                Case "I"
                    sb.Append( itm )
                Case "J"
                    sb.Append( itm )
                Case "F"
                    sb.Append( itm )
                Case "D"
                    sb.Append( itm )
                Case "C"
                    sb.Append( itm )
                Case "Z"
                    sb.Append( itm )
                Case "L"
                    If SubExists(itm,"ToJSON") Then
                        sb.Append( CallSub(itm,"ToJSON"))
                    Else
                        Select Case TypeName
                            Case "odp.eljaydelivery.openorderheader"
                                Dim ooh As OpenOrderHeader = itm
                                sb.Append( ooh.ToJSON )
                            Case "odp.eljaydelivery.openorderline"
                                Dim ool As OpenOrderLine = itm
                                sb.Append( ool.ToJSON )
                            Case Else
                                Log("        Case """ & TypeName & """")
                                Log("")
                        End Select
                       
                    End If
            End Select
        Next
        sb.Append(" ]")       
       
        Return sb.ToString
    End If
   
    Select Case rfl.TypeName
        Case "java.lang.String"
            Dim txt As String = obj
            If txt.StartsWith(QUOTE) And txt.EndsWith(QUOTE) Then Return txt
            Return QUOTE & obj & QUOTE

        Case "java.lang.boolean", "java.lang.byte", "java.lang.char", "java.lang.short", "java.lang.int", "java.lang.long", "java.lang.float", "java.lang.double"
            Return obj

        Case "java.lang.Boolean", "java.lang.Byte", "java.lang.Char", "java.lang.Short", "java.lang.Integer", "java.lang.Long", "java.lang.Float", "java.lang.Double"
            Return obj
       
        Case "java.util.ArrayList"
            Dim aryLst As JavaObject = obj
            Dim size As Int = aryLst.RunMethod("size", Array As Object())

            If size = 0 Then Return "[]"

            Dim sb As StringBuilder
            sb.Initialize
            sb.Append("[ ")
           
            For i = 0 To size-1
                If i > 0 Then sb.Append(", ")
                Dim itm As Object = aryLst.RunMethod("get", Array As Object(i))
                sb.Append(JSONValue(itm))
            Next
           
            sb.Append(" ]")
            Return sb.ToString
       
        Case "anywheresoftware.b4a.objects.collections.Map$MyMap"
            Return MapToJSON(obj)
               
        Case Else
            Log("        Case """ & rfl.TypeName & """")
            Log("")
           
            If SubExists(obj,"ToJSON") Then
                Return CallSub(obj,"ToJSON")
            End If

    End Select
   
End Sub
 
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
I'm not a fan of the pattern you're implementing here. The point of using the .toJSON() pattern was that it takes advantage of JSON's recursive validity. Every element in a JSON String is itself a valid JSON String. Let me give you an example:
B4X:
'Class module for a simple complex number class: Complex
Sub Class_Globals
    Private a, b as Double
End Sub
'...blah blah blah
Public Sub toJSON as String
     Dim m as Map
     m.Initialize
     m.Put("a", a)    'NOTE: primitives are just added. You can do this with Booleans, Strings, Doubles, Ints, etc....
     m.Put("b", b)
'.... create the JSON String from the Map and return it
End Sub
Now, a Complex Vector that has two Complex objects in it:
B4X:
'Class module for a complex vector class: ComplexVector
Sub Class_Globals
     Private x,y as Complex
End Sub
'....blah blah blah
Public Sub toJSON As String
     Dim m as Map
     m.Initialize
     m.Put("x", x.toJSON)    'NOTE: data members who are not primitives aren't added.
     m.Put("y", y.toJSON)    'Instead, the return value of their toJSON methods are added.
'.... create the JSON String from the Map and return it
End Sub

Note how every single class implements its own toJSON() method. If a class has a primitive data member, it just adds the primitive to the Map that will become the JSON String. If a class has an object as a data member, it adds that object's .toJSON() String to the Map. And this works recursively because a) every class implements its own .toJSON() method and b) every element in a JSON String is itself a valid JSON String. You shouldn't have to send an object to an external method that uses reflection to get its JSON value.

My apologies for the long-winded design lesson. It doesn't directly address your problem but I think if you implement it this way, you won't need a Globals code module that will cause you problems and it will greatly simplify your programming. No need for Reflection, Select-Case-ing on types, etc....
 
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
Also, note that the pattern I described above makes it trivially easy to implement an .InitializeFromJSON method for each class. No Reflection needed or anything.
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
I'm also not a fan of implementing this in the application layer. Under Visual Studio I use Newtonsoft and it does not care if members are primitives or class instances. It just works... done in 5 minutes! Took a few days to realize the limitations, and even longer to overcome them (still working on it).
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Maybe a hint. I inserted some lines of reflection prior to the crash point....
B4X:
Public Sub ToJSON As String
    Dim mp As Map
    mp.Initialize

    Dim rfl As Reflector
    Dim x As Object = rfl.GetActivityBA()
  
    mp.Put("OrderNumber",Globals.JSONValue(mOrderNumber))
    mp.Put("LineNumber",Globals.JSONValue(mLineNumber))

the rfl.GetActivityBA() is where it now crashes

B4X:
Error occurred on line: 287 (OpenOrderLine)
java.lang.NullPointerException
    at anywheresoftware.b4a.agraham.reflection.Reflection.GetActivityBA(Reflection.java:673)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:636)
           (etc.)

looked like the transpiled java source code was doing something similar where it crashed.
B4X:
     //BA.debugLineNum = 13434884;BA.debugLine="mp.Put(\"OrderNumber\",Globals.JSONValue(mOrderNumb";
    _mp.Put((Object)("OrderNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mordernumber))));

So the mere call to getActivityBA is where the null pointer exception comes from. I have no clue why though.

I changed the subject of my post to correspond with the spirit of the question based on new knowledge.
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Besides GetActivityBA, rfl.GetActivity call will also cause the exception to be thrown.
 
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
This is a stab in the dark but are you calling Globals.JSONValue from a Service? That would explain why the ActivityBA doesn't exist (i.e. returns a null pointer upon access).
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Thanks again for sticking with me on this. Not a service. I only tried the GetActivityBA call after seeing the transpiled java code. FWIW: It is not that it returns a null value, but rather it throws a null pointer exception during the call to GetActivity or GetActivityBA.
B4X:
     //BA.debugLineNum = 13434884;BA.debugLine="mp.Put(\"OrderNumber\",Globals.JSONValue(mOrderNumb";
    _mp.Put((Object)("OrderNumber"),(Object)(_globals._jsonvalue(getActivityBA(),(Object)(__ref._mordernumber))));
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Just to try a different thing

Where are you instantiating the class and its call to the method which leads to the crash? In Activity_Create or after it?
 
Upvote 0

Lee Gillie CCP

Active Member
Licensed User
Longtime User
Bingo! As originally mentioned there is a hierarchical structure of classes. This is built from a series of database queries. For all class instances referenced within the hiearchy I must create new instances of sub-structures. For ONE of these structures I neglected to call Initialize. I appreciate your taking time to reply JordiCP. It provided the perspective needed to see my error.

There is a lot you can do in a method for an uninitialized class, but apparently when calling out to a code module, it must be able to retrieve the Activity to pass as a parameter. So it hobbled along and worked for the most part, such as loading field values, but when it had to go outside of the class, it was at that point it crashed. It's like leaving a bit of doo-doo for yourself to step in later.
 
Last edited:
Upvote 0
Top