B4J Question Disconnect / close OKHttpClient

bdunkleysmith

Active Member
Licensed User
Longtime User
In this post https://www.b4x.com/android/forum/threads/consuming-streaming-data-from-http-call.111521/ I described resolving some issues I faced developing a B4J app which downloaded streaming data via http.

An OKHttpClient is created to provide an ongoing connection to the data server:

B4X:
Sub Class_Globals
    Private mTarget As Object
    Private mEventName As String
    Private hc As OkHttpClient
End Sub

Public Sub Initialize (TargetModule As Object, EventName As String)
    mTarget = TargetModule
    mEventName = EventName
    hc.Initialize("hc")
End Sub

Public Sub Connect(url As String)
    Dim req As OkHttpRequest
    req.InitializeGet(url)
    hc.Execute(req, 0)
End Sub

Data will continue to be streamed from the server indefinitely, however once I receive a particular response from the server I wish to disconnect from the server, perhaps by closing the OKHttpClient.

OkHttpResponse.release appeared promising, but I can't see how I could use that in the sub in the class handling OkHttpResponse:

B4X:
Private Sub hc_ResponseSuccess (Response As OkHttpResponse, TaskId As Int)
    CallSubDelayed2(mTarget, mEventName & "_Connected", True)
    Dim out As JavaObject
    out.InitializeNewInstance($"${PackageName}.fls$MyOutputStream"$, Array(Me))
    Response.GetAsynchronously("req", out, False, 0)
    Wait For req_StreamFinish (Success As Boolean, TaskId As Int)
    Log("Stream finish")   
End Sub

And I can't see any relevant function associated with OKHttpClient and so how do I disconnect so I no longer consume data?
 

bdunkleysmith

Active Member
Licensed User
Longtime User
Thanks @Erel but I still can't disconnect.

I inserted the suggested code into my fls class and added the Disconnect member:

B4X:
Sub Class_Globals
    Private mTarget As Object
    Private mEventName As String
    Private hc As OkHttpClient
    Private const PackageName As String = "b4j.example" '<------ change as needed
    Public charset As String = "UTF8"
    Private sb As StringBuilder
    Public gout As OutputStream
End Sub

Public Sub Initialize (TargetModule As Object, EventName As String)
    mTarget = TargetModule
    mEventName = EventName
    hc.Initialize("hc")
    sb.Initialize
End Sub

Public Sub Connect(url As String)
    Dim req As OkHttpRequest
    req.InitializeGet(url)
    hc.Execute(req, 0)
End Sub

Public Sub Disconnect
    Log("Sub Disconnect")
    Log("gout initialized: " & gout.IsInitialized)
    If gout.IsInitialized Then
        gout.Flush
        gout.Close
    End If
    Log("gout initialized: " & gout.IsInitialized)
End Sub

Private Sub hc_ResponseError (Response As OkHttpResponse, Reason As String, StatusCode As Int, TaskId As Int)
    Log("Error: " & StatusCode)
    Response.Release
End Sub

Private Sub hc_ResponseSuccess (Response As OkHttpResponse, TaskId As Int)
    CallSubDelayed2(mTarget, mEventName & "_Connected", True)
    Dim out As JavaObject
    out.InitializeNewInstance($"${PackageName}.fls$MyOutputStream"$, Array(Me))
    gout = out
    Response.GetAsynchronously("req", gout, False, 0)
    Wait For req_StreamFinish (Success As Boolean, TaskId As Int)
    Log("Stream finish")
End Sub

Private Sub Data_Available (Buffer() As Byte)
    Dim newDataStart As Int = sb.Length
    sb.Append(BytesToString(Buffer, 0, Buffer.Length, charset))
    Dim s As String = sb.ToString
    Dim start As Int = 0
    For i = newDataStart To s.Length - 1
        Dim c As Char = s.CharAt(i)
        If i = 0 And c = Chr(10) Then '\n...
            start = 1 'might be a broken end of line character
            Continue
        End If
        If c = Chr(10) Then '\n
            CallSubDelayed2(mTarget, mEventName & "_NewText", s.SubString2(start, i))
            start = i + 1
        Else If c = Chr(13) Then '\r
            CallSubDelayed2(mTarget, mEventName & "_NewText", s.SubString2(start, i))
            If i < s.Length - 1 And s.CharAt(i + 1) = Chr(10) Then '\r\n
                i = i + 1
            End If
            start = i + 1
        End If
    Next
    If start > 0 Then sb.Remove(0, start)
End Sub

#if Java
import java.io.*;
public static class MyOutputStream extends OutputStream {
    B4AClass cls;
    public MyOutputStream (B4AClass cls) {
        this.cls = cls;
    }
    
    public void write(int b) throws IOException {
        cls.getBA().raiseEventFromDifferentThread (null, null, 0, "data_available", true, new Object[] {new byte[] {(byte)b}});
    }
    public void write(byte b[], int off, int len) throws IOException {
        byte[] bb = new byte[len];
        System.arraycopy(b, off, bb, 0, len);
        cls.getBA().raiseEventFromDifferentThread (null, null, 0, "data_available", true, new Object[] {bb});
    }
}
#End If

But the http connection remains open after the Disconnect sub is executed as evidenced by the log:

B4X:
Connection Status: CONNECTED
Parsing teams data
Parsing boxscore data
Status: complete | Period: 4 | Time: 00:00:00
Sub Disconnect
gout initialized: true
gout initialized: true
Connection Status: CONNECTED
Connection Status: CONNECTED
Connection Status: CONNECTED
Connection Status: CONNECTED

showing that connection status responses continue to be received after the Disconnect code has been executed. As can be seen, I check that gout is initialized before closing it and even flushed it in case that was required before close.
 
Upvote 0

bdunkleysmith

Active Member
Licensed User
Longtime User
Thanks @Erel

It's amazing what happens with that addition of

B4X:
    public void close() {
        closed = true;
    }

Works perfectly now thank you!
 
Upvote 0

bdunkleysmith

Active Member
Licensed User
Longtime User
The important part is the test of this variable and the exception that is thrown when it is closed.

Yes @Erel that part is also important and this is how I used your concept for my FLS class.

B4X:
Sub Class_Globals
    Private mTarget As Object
    Private mEventName As String
    Private hc As OkHttpClient
    Private const PackageName As String = "b4j.example" '<------ change as needed
    Private charset As String = "UTF8"
    Private sb As StringBuilder
    Private gout As OutputStream
End Sub

Public Sub Initialize (TargetModule As Object, EventName As String)
    mTarget = TargetModule
    mEventName = EventName
    hc.Initialize("hc")
    sb.Initialize
End Sub

Public Sub Connect(url As String)
    Dim req As OkHttpRequest
    req.InitializeGet(url)
    hc.Execute(req, 0)
End Sub

Public Sub Disconnect
    If gout.IsInitialized Then gout.Close
End Sub

Private Sub hc_ResponseError (Response As OkHttpResponse, Reason As String, StatusCode As Int, TaskId As Int)
    Log("Error: " & StatusCode)
    Response.Release
End Sub

Private Sub hc_ResponseSuccess (Response As OkHttpResponse, TaskId As Int)
    CallSubDelayed2(mTarget, mEventName & "_Connected", True)
    Dim out As JavaObject
    out.InitializeNewInstance($"${PackageName}.fls$MyOutputStream"$, Array(Me))
    gout = out
    Response.GetAsynchronously("req", out, False, 0)
    Wait For req_StreamFinish (Success As Boolean, TaskId As Int)
    Log("Stream finish")
    CallSubDelayed2(mTarget, mEventName & "_Disconnected", True)
End Sub

Private Sub Data_Available (Buffer() As Byte)
    Dim newDataStart As Int = sb.Length
    sb.Append(BytesToString(Buffer, 0, Buffer.Length, charset))
    Dim s As String = sb.ToString
    Dim start As Int = 0
    For i = newDataStart To s.Length - 1
        Dim c As Char = s.CharAt(i)
        If i = 0 And c = Chr(10) Then '\n...
            start = 1 'might be a broken end of line character
            Continue
        End If
        If c = Chr(10) Then '\n
            CallSubDelayed2(mTarget, mEventName & "_NewText", s.SubString2(start, i))
            start = i + 1
        Else If c = Chr(13) Then '\r
            CallSubDelayed2(mTarget, mEventName & "_NewText", s.SubString2(start, i))
            If i < s.Length - 1 And s.CharAt(i + 1) = Chr(10) Then '\r\n
                i = i + 1
            End If
            start = i + 1
        End If
    Next
    If start > 0 Then sb.Remove(0, start)
End Sub

#if Java
import java.io.*;
public static class MyOutputStream extends OutputStream {
    B4AClass cls;
    private boolean closed;
    public MyOutputStream (B4AClass cls) {
        this.cls = cls;
    }
   
    public void write(int b) throws IOException {
        if (closed)
            throw new IOException("closed");
        cls.getBA().raiseEventFromDifferentThread (null, null, 0, "data_available", true, new Object[] {new byte[] {(byte)b}});
    }
    public void write(byte b[], int off, int len) throws IOException {
        if (closed)
            throw new IOException("closed");
        byte[] bb = new byte[len];
        System.arraycopy(b, off, bb, 0, len);
        cls.getBA().raiseEventFromDifferentThread (null, null, 0, "data_available", true, new Object[] {bb});
    }
    public void close() {
        closed = true;
    }
}
#End If

I've added a _Disconnected event after the _StreamFinish event is triggered so I can take consequential action back in Main once closure of the stream is confirmed. I had already added a _Connected event so similarly I can take consequential action back in Main once a connection to the stream is confirmed, indicated by the _ResponseSuccess event being thrown.

My proof of concept app is now working thanks to the great resources this community provides.

Untitled.png
 
Upvote 0
Top