Android Question Extracting data from a Java ArrayList

Johan Schoeman

Expert
Licensed User
Longtime User
I am a bit lost here...have searched the forum but could thus far not find a suitable solution.

I have an ArrayList in a wrapper that is defined as follows:
B4X:
ArrayList<ClientScanResult> clients

It is returned to the B4A project via:
B4X:
ba.raiseEvent2(ba, false, eventName + "_scan_result", true, new Object[] {clients});

My B4A event is as follows:
B4X:
Sub hotspot_scan_result (clients As Object)

When I log "clients" in the B4A code I get for eg:
B4X:
[main.java.ClientScanResult@42c97f00]

Each "clients" object consists of 3 x Strings and 1 x Boolean. How do I extract this from the "clients" object that is returned to B4A?

Can someone please advise on this?
Thanks
 

Roycefer

Well-Known Member
Licensed User
Longtime User
Do you have access to the main.java.ClientScanResult class in your B4A code? If so, try the following:
B4X:
Sub hotspot_scan_result (clients As Object)
    Dim clientsList As List = clients
    For Each csr As ClientScanResult In clientsList
        'access the member data of csr, for example:
        Log(csr.String1 & " " & csr.String2 & " " & csr.String3 & " " & csr.TheBooleanValue)
        'or whatever, depending on how the data members are named. 
    Next
End Sub

The above example will require casting an ArrayList to a B4X List. I don't recall if this will work. If it doesn't, you have a couple options. In your Java code, use a B4X List instead of an ArrayList. Or, use JavaObject inside the B4A event to access the "get" and "length" methods of the ArrayList and then run a For-Loop to "get" all of the elements in the ArrayList. Of course, you'll still need to be able to create instances of the ClientScanResult class.
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Hi,

I tested it, with an arrayList of strings instead of a custom class, with these results:

The inline java part
B4X:
public void test(){

        ArrayList<String> clients = new ArrayList<String>(3);
        clients.add("ONE");
        clients.add("TWO");
        clients.add("THREE");
        //ba.raiseEvent2(ba, false, "prova_result", true, new Object{clients}); 
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}


B4A part
B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients)   
End Sub

The Logs are [ONE,TWO,THREE] as expected.

I suppose that if you have a custom type ArrayList, its fields can only be accessed by "GetField" instance.

Test with this simple case to see if the difference is the "simple type" vs the "custom class type" of the ArrayList or there is something else
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
Hi,

I tested it, with an arrayList of strings instead of a custom class, with these results:

The inline java part
B4X:
public void test(){

        ArrayList<String> clients = new ArrayList<String>(3);
        clients.add("ONE");
        clients.add("TWO");
        clients.add("THREE");
        //ba.raiseEvent2(ba, false, "prova_result", true, new Object{clients});
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}


B4A part
B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients)  
End Sub

The Logs are [ONE,TWO,THREE] as expected.

I suppose that if you have a custom type ArrayList, its fields can only be accessed by "GetField" instance.

Test with this simple case to see if the difference is the "simple type" vs the "custom class type" of the ArrayList or there is something else
Thanks Jordi - it is a custom class type so it seems that GetField is probably the way to go then...
ArrayList<ClientScanResult> clients
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
Hi,

I tested it, with an arrayList of strings instead of a custom class, with these results:

The inline java part
B4X:
public void test(){

        ArrayList<String> clients = new ArrayList<String>(3);
        clients.add("ONE");
        clients.add("TWO");
        clients.add("THREE");
        //ba.raiseEvent2(ba, false, "prova_result", true, new Object{clients});
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}


B4A part
B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients)  
End Sub

The Logs are [ONE,TWO,THREE] as expected.

I suppose that if you have a custom type ArrayList, its fields can only be accessed by "GetField" instance.

Test with this simple case to see if the difference is the "simple type" vs the "custom class type" of the ArrayList or there is something else
Jordi, still a bit confused here...so how do you now extract each of the elements in the B4A sub and assign each of them to a separate String variable? Do you define a B4A List and set the List = clients ?
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
This is how I have solved it. Probably not the "purest" way of doing it but it is much easier having to take care of a Boolean value that was converted to a String (in the Java code) when it arrives in the B4A event than to try and sort out a "mixed basket of fruits" that gets passed to the B4A event from the wrapper:

The Java code:
B4X:
    public void scan() {
        wifiApManager.getClientList(false, new FinishScanListener() {

            @Override
            public void onFinishScan(final ArrayList<ClientScanResult> clients) {
                                ArrayList<String> mylist = new ArrayList<String>();
                for (ClientScanResult clientScanResult : clients) {
                                        mylist.add(clientScanResult.getIpAddr());
                                        mylist.add(clientScanResult.getDevice());
                                        mylist.add(clientScanResult.getHWAddr());
                                        //Convert the boolean value of "isReachable()" to a String before adding it to mylist
                                        //Now we only need to return a basket of apples and not a basket of mixed fruits
                                        //It can then be assigned to a B4A list in the B4A code
                                        mylist.add(String.valueOf(clientScanResult.isReachable()));
                }  
                                if (ba.subExists(eventName + "_scan_result")) {
                                   ba.raiseEvent2(ba, false, eventName + "_scan_result", true, new Object[] {mylist});
                                }
            }
        });
    }

The B4A code:
B4X:
Sub hotspot_scan_result (mylist As Object)
   
    Dim ml As List
    ml= mylist
   
    For i = 0 To ml.Size - 1
        If (i+1) mod 4 = 1 Then Log("IpAddr = " & ml.Get(i))
        If (i+1) mod 4 = 2 Then Log("Device = " & ml.Get(i))
        If (i+1) mod 4 = 3 Then Log("HWAddr = " & ml.Get(i))       
        If (i+1) mod 4 = 0 Then Log("Reachable = " & ml.Get(i))       
        If (i+1) mod 4 = 0 Then Log(" ")               
    Next

End Sub
 
Upvote 0

Informatix

Expert
Licensed User
Longtime User
This is how I have solved it. Probably not the "purest" way of doing it but it is much easier having to take care of a Boolean value that was converted to a String (in the Java code) when it arrives in the B4A event than to try and sort out a "mixed basket of fruits" that gets passed to the B4A event from the wrapper:

The Java code:
B4X:
    public void scan() {
        wifiApManager.getClientList(false, new FinishScanListener() {

            @Override
            public void onFinishScan(final ArrayList<ClientScanResult> clients) {
                                ArrayList<String> mylist = new ArrayList<String>();
                for (ClientScanResult clientScanResult : clients) {
                                        mylist.add(clientScanResult.getIpAddr());
                                        mylist.add(clientScanResult.getDevice());
                                        mylist.add(clientScanResult.getHWAddr());
                                        //Convert the boolean value of "isReachable()" to a String before adding it to mylist
                                        //Now we only need to return a basket of apples and not a basket of mixed fruits
                                        //It can then be assigned to a B4A list in the B4A code
                                        mylist.add(String.valueOf(clientScanResult.isReachable()));
                }
                                if (ba.subExists(eventName + "_scan_result")) {
                                   ba.raiseEvent2(ba, false, eventName + "_scan_result", true, new Object[] {mylist});
                                }
            }
        });
    }

The B4A code:
B4X:
Sub hotspot_scan_result (mylist As Object)
 
    Dim ml As List
    ml= mylist
 
    For i = 0 To ml.Size - 1
        If (i+1) mod 4 = 1 Then Log("IpAddr = " & ml.Get(i))
        If (i+1) mod 4 = 2 Then Log("Device = " & ml.Get(i))
        If (i+1) mod 4 = 3 Then Log("HWAddr = " & ml.Get(i))     
        If (i+1) mod 4 = 0 Then Log("Reachable = " & ml.Get(i))     
        If (i+1) mod 4 = 0 Then Log(" ")             
    Next

End Sub
Instead of creating an ArrayList of strings, you should create a List:
B4X:
anywheresoftware.b4a.objects.collections.List mylist = new anywheresoftware.b4a.objects.collections.List();
mylist.Initialize();
So the object passed to the event is a B4A List.
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
Instead of creating an ArrayList of strings, you should create a List:
B4X:
anywheresoftware.b4a.objects.collections.List mylist = new anywheresoftware.b4a.objects.collections.List();
mylist.Initialize();
So the object passed to the event is a B4A List.
Will that allow a mixed list of variable types to be easily extracted in the B4A code? Or would I still have to convert the Boolean to a String in the Java code to make it easier? I read somewhere in the forum that Java's ArrayList is the equivalent of a B4A list. Is that not the case?
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
This has worked for me (class fields need to be pulbic, otherwise you should work with getters)

B4X:
public class client {

    public String s1;
    public String s2;
    public Boolean b;

    public client(String s1_0,String s2_0, Boolean b_0) {
        s1=new String(s1_0);
        s2=new String(s2_0);
        b = b_0;
    }
}
public void test(){

        ArrayList<client> clients = new ArrayList<client>();
        clients.add( new client("Client1Name","Client1Surname",true));
        clients.add( new client("Client2Name","Client2Surname",false));
        clients.add( new client("Client3Name","Client3Surname",true));
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}

In B4A
B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients)  
    Dim a As List
    a=clients
    For k=0 To a.Size-1
        Dim J As JavaObject = a.Get(k)
        Log("Client:"&J.GetField("s1")&" "&J.GetField("s2")&" "&J.GetField("b"))
    Next
End Sub
 
Upvote 0

Informatix

Expert
Licensed User
Longtime User
Will that allow a mixed list of variable types to be easily extracted in the B4A code? Or would I still have to convert the Boolean to a String in the Java code to make it easier? I read somewhere in the forum that Java's ArrayList is the equivalent of a B4A list. Is that not the case?
The primitive types do not need to be wrapped so if you have a string or a boolean in the original list, you can keep it as is. And the B4A list can contain mixed types.

List wraps an ArrayList. If your ArrayList is an ArrayList<Object>, you can do:
mylist.setObject(arrList);
instead of adding (and thus copying) each item in a loop.
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
This has worked for me (class fields need to be pulbic, otherwise you should work with getters)

B4X:
public class client {

    public String s1;
    public String s2;
    public Boolean b;

    public client(String s1_0,String s2_0, Boolean b_0) {
        s1=new String(s1_0);
        s2=new String(s2_0);
        b = b_0;
    }
}
public void test(){

        ArrayList<client> clients = new ArrayList<client>();
        clients.add( new client("Client1Name","Client1Surname",true));
        clients.add( new client("Client2Name","Client2Surname",false));
        clients.add( new client("Client3Name","Client3Surname",true));
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}

In B4A
B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients) 
    Dim a As List
    a=clients
    For k=0 To a.Size-1
        Dim J As JavaObject = a.Get(k)
        Log("Client:"&J.GetField("s1")&" "&J.GetField("s2")&" "&J.GetField("b"))
    Next
End Sub
Jordi, I like what you have done above. I just don't understand the rationale behind the class variables having to be public when you are returning "clients" which is an ArrayList. Can you please elaborate on this?
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
The primitive types do not need to be wrapped so if you have a string or a boolean in the original list, you can keep it as is. And the B4A list can contain mixed types.

List wraps an ArrayList. If your ArrayList is an ArrayList<Object>, you can do:
mylist.setObject(arrList);
instead of adding (and thus copying) each item in a loop.
Thanks @Informatix. Will definateley give it a try!
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Jordi, I like what you have done above. I just don't understand the rationale behind the class variables having to be public when you are returning "clients" which is an ArrayList. Can you please elaborate on this?
The arrayLists return the group of client instances, it is a "container". But as they are not primitive, you need a way to access its members in the B4A Sub. GetField will not work for instance if S2 is declared as private

I have modified the example to reflect this. As S2 is private, for convenience a "GetS2" function has been declared to have access to it

B4X:
Sub prova_result(clients As Object)
    Log("Result is:"&clients)  
    Dim a As List
    a=clients
    For k=0 To a.Size-1
        Dim J As JavaObject = a.Get(k)

        'Log("Client:"&J.GetField("s1")&" "&J.GetField("s2")&" "&J.GetField("b")) 'This will only work if all s1,s2,and b are publc members

        Dim s2 As String =J.RunMethod("getS2",Null)
        Log("Client:"&J.GetField("s1")&" "&s2&" "&J.GetField("b"))
    Next
End Sub

The java part
B4X:
public class client {

    public String s1;
    private String s2;    //Now it is private
    public Boolean b;

    public client(String s1_0,String s2_0, Boolean b_0) {
        s1=new String(s1_0);
        s2=new String(s2_0);
        b = b_0;
    }
    public String getS2(){
        return s2;
    }
}
public void test(){

        ArrayList<client> clients = new ArrayList<client>();
        clients.add( new client("Client1Name","Client1Surname",true));
        clients.add( new client("Client2Name","Client2Surname",false));
        clients.add( new client("Client3Name","Client3Surname",true));
        processBA.raiseEvent(null, "prova_result", new Object[]{clients});
}
 
Upvote 0

Informatix

Expert
Licensed User
Longtime User
But as they are not primitive, you need a way to access its members in the B4A Sub.
It seems that the ClientScanResult class contains three strings and a boolean, so it's directly compatible with B4A. But, as a general rule of thumb, I prefer to create a wrapper as your client class so that all members can be accessed easily, with a significant name, from B4A. Another solution, as I suggested above, is to use a map with constant strings for the keys (to name the values).
 
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
It seems that the ClientScanResult class contains three strings and a boolean, so it's directly compatible with B4A. But, as a general rule of thumb, I prefer to create a wrapper as your client class so that all members can be accessed easily, with a significant name, from B4A. Another solution, as I suggested above, is to use a map with constant strings for the keys (to name the values).
So in all honesty, is the way I have solved it a "dumb" way of doing it?
 
Upvote 0

Informatix

Expert
Licensed User
Longtime User
So in all honesty, is the way I have solved it a "dumb" way of doing it?
No, there are multiple ways to do it. What I suggested in post #5 is just more convenient for the end user as he does not have to "decode" the result. It's a bit of extra work for you but very convenient for all your users. It's better to pass a B4A list to the event than a Java object, and better to access properties with their name (e.g. MyClientScanResultWrapper.IpAddr) than to get them with something like If (i+1) mod4 = 1 Then IpAddr = ml.Get(i).
 
Upvote 0
Top