B4J Tutorial [Server] Send and receive objects

Erel

Administrator
Staff member
Licensed User
This is an old example. Better use B4XSerializator to serialize objects.

One of the nice things about using B4J server as the backend of B4A applications is that you can send and receive objects such as custom types and collections of custom types instead of working with bytes or strings.

In this example we have two custom types:
B4X:
Type Order(ProductKey As String, Amount As Int, Comment As String)
Type OrderResult(NumberOfOrders As Int, Success As Boolean)
These types are declared both in the server and the client applications.

The client will send a list of Orders and the server will return an OrderResult object. The serialization of the objects is done with RandomAccessFile.WriteObject / ReadObject. It supports all primitive types, custom types, arrays and collections. Note that Bitmaps are not supported.

Client Code

B4X:
Sub Process_Globals
   Type Order(ProductKey As String, Amount As Int, Comment As String)
   Type OrderResult(NumberOfOrders As Int, Success As Boolean)
 
   Private raf As RandomAccessFile
   'server link
   Private link As String = "http://192.168.0.100:51042/order"
End Sub

Sub Activity_Create(FirstTime As Boolean)
   If FirstTime Then
     raf.Initialize(File.DirInternal, "temp", False)
   End If
 
   Dim Orders As List
   Orders.Initialize
   For i = 1 To 20
     Dim o As Order
     o.Initialize
     o.ProductKey = "Item #" & i
     o.Amount = 7
     o.Comment = "abcdef"
     Orders.Add(o)
   Next
   SendObject(Orders)
End Sub

Sub SendObject (Obj As Object)
   raf.WriteObject(Obj, True, 0)
   Dim size As Int = raf.CurrentPosition
   Dim data(size) As Byte
   raf.CurrentPosition = 0
   Do While raf.CurrentPosition < size
     raf.ReadBytes(data, raf.CurrentPosition, size - raf.CurrentPosition, _
       raf.CurrentPosition)
   Loop
 
   Dim j As HttpJob
   j.Initialize("send object", Me)
   j.PostBytes(link, data)
End Sub

Sub ReadObject (In As InputStream) As Object
   Dim out As OutputStream
   out.InitializeToBytesArray(0)
   File.Copy2(In, out)
   Dim raf2 As RandomAccessFile
   raf2.Initialize3(out.ToBytesArray, False)
   Dim res As Object = raf2.ReadObject(0)
   raf2.Close
   Return res
End Sub

Sub JobDone(j As HttpJob)
   If j.Success Then
     Dim result As OrderResult = ReadObject(j.GetInputStream)
     Log(result)
   Else
     Log("Error: " & j.ErrorMessage)
   End If
   j.Release
End Sub
Server code

Main module:
B4X:
Sub Process_Globals
   Type Order(ProductKey As String, Amount As Int, Comment As String)
   Type OrderResult(NumberOfOrders As Int, Success As Boolean)
   Private srvr As Server
End Sub

Sub AppStart (Args() As String)
   srvr.Initialize("")
   srvr.Port = 51042
   srvr.AddHandler("/order", "OrderHandler", False)
   srvr.Start
   StartMessageLoop
End Sub
OrderHander class:
B4X:
Sub Class_Globals
   'The buffer must be large enough to hold the result object.
   Private bufferSize As Int = 1000
End Sub

Public Sub Initialize

End Sub

Sub Handle(req As ServletRequest, resp As ServletResponse)
   Dim orders As List = ReadObject(req.InputStream)
   Dim result As OrderResult
   result.Initialize
   For Each o As Order In orders
     'work with the order
     Log(o.ProductKey & ", " & o.Amount & ", " & o.Comment)
     result.NumberOfOrders = result.NumberOfOrders + 1
   Next
   result.Success = True
   SendObject(result, resp.OutputStream)
End Sub

Sub ReadObject (In As InputStream) As Object
   Dim out As OutputStream
   out.InitializeToBytesArray(0)
   File.Copy2(In, out)
   Dim raf2 As RandomAccessFile
   raf2.Initialize3(out.ToBytesArray, False)
   Dim res As Object = raf2.ReadObject(0)
   raf2.Close
   Return res
End Sub

Sub SendObject (Obj As Object, Out As OutputStream)
   Dim raf As RandomAccessFile
   Dim buffer(bufferSize) As Byte
   raf.Initialize3(buffer, False)
   raf.WriteObject(Obj, True, 0)
   Out.WriteBytes(buffer, 0, raf.CurrentPosition)
End Sub
Note that the server writes the object into a temporary buffer. The buffer must be large enough in order to hold the serialized data. You can check raf.CurrentPosition after writing the object to get an estimation of the required size. Another option is to use a temporary file as done in the client. However for this to work properly you need to set the handler to run in single threaded mode.

Note that you will see the following message in the logs:
Class not found: b4a.example.main$_order, trying: b4j.example.main$_order
You can ignore it. It happens on the first time that a type with a different package is discovered.
 
Last edited:

Asim A Baki

Active Member
Licensed User
You don't need to change anything. The above code can handle multiple clients.
Sorry I tried but it didn't work, the first client is only the one can send

anyhow, I already handled this by mexing some code from CCTV example
 

driesvp

Member
Licensed User
Dear,

When receiving an object from my B4JServer to my Android I receive an error: java.lang.classnotfoundexception: Veos.JavaServer.main$_dbresult.

The received object refers to B4JServer nevertheless it's downloaded on my phone. On the phone I declared dbresult in Process_globals of Main.

How to solve this?

Thanks!
 

driesvp

Member
Licensed User
I will post the message later this evening but I already can tell you that the app crashes.
It's the only error message (in red) that I receive.
 

driesvp

Member
Licensed User
When using this routine, it doesn't work when the received recordset is too big. Anyone an idea how to solve this?

B4X:
Public Sub ReadObject (In As InputStream) As Object
    Try
        Dim out As OutputStream
        out.InitializeToBytesArray(0)
        File.Copy2(In, out)
        Dim raf2 As RandomAccessFile
        raf2.Initialize3(out.ToBytesArray, False)
        Dim res As Object = raf2.ReadObject(0)
        raf2.Close
        Return res
    Catch
        res=""
    End Try
  
End Sub
 

driesvp

Member
Licensed User
I receive this error message:

LastException java.lang.ArrayIndexOutOfBoundsException: length=0; index=0

It's happening on this line:
Dim res As Object = raf2.ReadObject(0)
 

driesvp

Member
Licensed User
Thanks! :)

LogCat connected to: B4A-Bridge: Sony Ericsson LT26i
--------- beginning of /dev/log/system
--------- beginning of /dev/log/main
** Service (databaseconnectie) Start **
** Service (databaseconnectie) Start **
** Service (databaseconnectie) Start **
~i:** Activity (main) Resume **
~i:** Activity (main) Pause, UserClosed = false **
~i:** Activity (main) Create, isFirst = false **
~i:** Activity (main) Resume **
~i:** Activity (main) Pause, UserClosed = false **
~i:** Activity (main) Create, isFirst = false **
~i:** Activity (main) Resume **
~i:** Service (service1) Create **
~i:** Service (service1) Start **
Connected to B4A-Bridge (Wifi)
** Service (databaseconnectie) Start **
Installing file.
~i:** Activity (main) Pause, UserClosed = false **
PackageAdded: package:Veos.OnderhoudSmeren
** Activity (main) Create, isFirst = true **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Resume **
** Service (databaseconnectie) Create **
** Service (databaseconnectie) Create **
** Service (databaseconnectie) Start **
** Service (databaseconnectie) Start **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Pause, UserClosed = false **
** Activity (overzichtwerkbonnen) Create, isFirst = true **
** Activity (overzichtwerkbonnen) Create, isFirst = true **
** Activity (overzichtwerkbonnen) Resume **
** Activity (overzichtwerkbonnen) Resume **
startService: class Veos.OnderhoudSmeren.httputils2service
startService: class Veos.OnderhoudSmeren.httputils2service
** Service (httputils2service) Create **
** Service (httputils2service) Create **
** Service (httputils2service) Start **
** Service (httputils2service) Start **
srobjects_readobject (B4A line: 34)
Dim res As Object = raf2.ReadObject(0)
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile$ByteArrayChannel.read(RandomAccessFile.java:665)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.ReadInt(RandomAccessFile.java:137)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.readHelper(RandomAccessFile.java:375)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.ReadObject(RandomAccessFile.java:364)
at Veos.OnderhoudSmeren.srobjects._readobject(srobjects.java:106)
at Veos.OnderhoudSmeren.objectedit._jobuitgevoerd(objectedit.java:53)
at Veos.OnderhoudSmeren.srobjects._jobdone(srobjects.java:68)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:167)
at anywheresoftware.b4a.keywords.Common$4.run(Common.java:885)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:213)
at android.app.ActivityThread.main(ActivityThread.java:4787)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
at dalvik.system.NativeStart.main(Native Method)
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
srobjects_readobject (B4A line: 34)
Dim res As Object = raf2.ReadObject(0)
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile$ByteArrayChannel.read(RandomAccessFile.java:665)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.ReadInt(RandomAccessFile.java:137)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.readHelper(RandomAccessFile.java:375)
at anywheresoftware.b4a.randomaccessfile.RandomAccessFile.ReadObject(RandomAccessFile.java:364)
at Veos.OnderhoudSmeren.srobjects._readobject(srobjects.java:106)
at Veos.OnderhoudSmeren.objectedit._jobuitgevoerd(objectedit.java:53)
at Veos.OnderhoudSmeren.srobjects._jobdone(srobjects.java:68)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:167)
at anywheresoftware.b4a.keywords.Common$4.run(Common.java:885)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:213)
at android.app.ActivityThread.main(ActivityThread.java:4787)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
at dalvik.system.NativeStart.main(Native Method)
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
** Activity (overzichtwerkbonnen) Pause, UserClosed = false **
** Activity (overzichtwerkbonnen) Pause, UserClosed = false **
** Activity (overzichtwerkbonnen) Create, isFirst = false **
** Activity (overzichtwerkbonnen) Create, isFirst = false **
~i:** Activity (main) Resume **
~i:** Activity (main) Pause, UserClosed = false **
~i:** Activity (main) Create, isFirst = false **
~i:** Activity (main) Resume **
~i:** Activity (main) Pause, UserClosed = false **
Connected to B4A-Bridge (Wifi)
sending message to waiting queue (CallSubDelayed - UpdateStatus)
Connected to B4A-Bridge (Wifi)
sending message to waiting queue (CallSubDelayed - UpdateStatus)
 

driesvp

Member
Licensed User
ok, then it must have something to do with the code on the B4J server side. When I limit the query to max 20 records it's working.
Is it possible that the buffersize need to be enlarged on the server?

B4X:
Sub SendObject (Obj As Object, Out As OutputStream)
    Dim raf As RandomAccessFile
    Dim bufferSize As Int = 1000
    Dim buffer(bufferSize) As Byte
    raf.Initialize3(buffer, False)
    raf.WriteObject(Obj, True, 0)
    Out.WriteBytes(buffer, 0, raf.CurrentPosition)
   
End Sub
 
Top