Java Question Raise events to a different module in Java

max123

Well-Known Member
Licensed User
Longtime User
Hi all, it is possibile to raise an event in different module than a calling module ?

I search to explain better, I wrote a class in java that add a WebChromeClient to a webview and add some funtionalities (as main goal a simple JS console).
Next I compiled this class to a B4A library, created xml and jar files and put in B4A Addictional libraries.

After this I started a new B4A project, then created a class that manage UI (for a JS console), this class use a WebChromeClient class to receive callbacks,
then send these to the Main.

When all worked I've compiled together as library directly from B4A IDE (Compile as library).

Substantially I have this:
Main > JSConsole Class > WebChromeClient

The main initialize the JSConsole class and this class initialize and use a WebChromeClient, the class JSConsole in the middle acts as bridge from the WebChromeClient and the Main.

In a WebChromeClient I actually use onConsoleMessage event to trap consoles messages, then I use RaiseEvent to send to JSConsole class and this send it back to the Main with CallSub().

All this is slow, sometime it miss console messages, I even collected messages using StringBuilder, this help but not too mutch.
I even changed the original CallSubDelayed in the JSConsole class, with CallSub, the callback function receive from a WebChromeClient and send to the Main,
this helped too.

The main problem is that I'm not sure this is the right way to do it....
What I want is to raise events from the WebChromeClient directly to the Main (or a calling module in general) without involve JSConsole class in the middle that only manage UI, so even reduce calls to functions.

Because I even implemented Javascript Interface with the command addJavascriptInterface (like WebViewExtras and UltimateWebView) with even return callbacks and multiple arguments I need to call a function directly to the Main that is exposed to the end user.

Is that possible and if yes how ?

I've attached some relevant code.

Many thanks

Some relevant parts of B4AWebChromeClient wrapper:
public void Initialize(final BA mBA, final Object CallbackModule, WebView webview, String event) {
        ba = mBA;
        this.webview = webview;
        this.eventName = event.toLowerCase(BA.cul);
        isInitialized = true;
  
//        BA.Log("isInitialized: " + isInitialized);
  
        this.webview.setWebChromeClient(new WebChromeClient() {
      
            @Override
            public boolean onConsoleMessage(ConsoleMessage cm) {
                //if (ba.subExists(eventName + "_message")) {
                   // BA.Log(System.currentTimeMillis() + " JAVA: " + cm.message());
                    ba.raiseEvent(this, eventName + "_message", new Object[] {cm.message(), cm.sourceId(), (int)cm.lineNumber(), cm.messageLevel().toString()});
                //}
                return true;
            }

            @Override
            public void onProgressChanged(WebView webview, int newProgress) {
              if (webview.getTitle() == null || webview.getOriginalUrl() == null) return;
                      
              //if (ba.subExists(eventName + "_progress")) {
                  //BA.Log("Raise Event: " + fullEventName2);
                  ba.raiseEvent(this, eventName + "_progress", newProgress, webview.getTitle(), webview.getOriginalUrl());
              //} //else {
                  //BA.Log("CALLER Event Sub do not exists: " + eventName + "_progress");
              //}
            }   
        });
    }
//........................
//........................
public  void AddJavascriptInterface(String InterfaceName) {
        this.webview.getSettings().setJavaScriptEnabled(true);
  
        this.webview.addJavascriptInterface(new Object() {
      
          private int mTaskId = 0;
    
          @JavascriptInterface
          public Object CallSub(String SubName, boolean pCallUIThread) {
            String lSubName = SubName.toLowerCase();
            BA.Log("Call a B4A sub without arguments: " + lSubName);
      
            if (ba.subExists(lSubName)) {
              if (pCallUIThread) {
                  BA.Log("Expecting return from " + SubName);
                  return (Object)ba.raiseEventFromDifferentThread(this, this, mTaskId++, lSubName, false, new Object[0]);
              } else {
                  BA.Log("DO NOT Expecting return from " + SubName);
                  return (Object)ba.raiseEvent(this, lSubName, new Object[0]);
              }
            }
      
            BA.Log("JavascriptInterface error: " + SubName + " does not exist");
            return "JavascriptInterface error: " + SubName + " does not exist";
          }
    
          @JavascriptInterface
          public Object CallSub(String SubName, boolean pCallUIThread, Object parameter1) {
            String lSubName = SubName.toLowerCase();
            BA.Log("Call a B4A sub with one argument: " + lSubName);
      
            if (ba.subExists(lSubName)) {
              Object[] parameter = { parameter1 };
              if (pCallUIThread) {
                BA.Log("Expecting return from " + SubName);
                return (Object)ba.raiseEventFromDifferentThread(this, this, mTaskId++, lSubName, false, parameter);
              } else {
                BA.Log("DO NOT Expecting return from " + SubName);
                return (Object)ba.raiseEvent(this, lSubName, parameter);
              }
            }
//........................
//........................
    public void ExecuteJavascript (String Script) { 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){  // KITKAT API 19
            this.webview.evaluateJavascript(Script, null);  // new ValueCallback<String>() {
        } else {
            this.webview.loadUrl("javascript:" + Script);
        }
    }

Some relevant parts of JSConsole class:
Private Sub Class_Globals
    Private WebChromeClient As B4AWebChromeClient
End Sub

Public Sub Initialize(Parent As Panel, Module As Object, EventName As String, WebView As WebView, Height As Int, Duration As Int)
   '.................
   WebChromeClient.Initialize(WebView, "ChromeClient")
   '.................
End Sub

Public Sub AddJavascriptInterface(InterfaceName As String)
    WebChromeClient.AddJavascriptInterface(InterfaceName)
End Sub

Public Sub ExecuteJavascript(Script As String)
    WebChromeClient.ExecuteJavascript(Script)
End Sub

Private Sub ChromeClient_Message (Message As String, SourceID As String, LineNumber As Int, Level As String)
    If SubExists(mModule, mEventName & "_Message") = False Then
        '        Log("SUB " & mModule & mEventName & "_Message DO NOT EXISTS")
        Return
    End If
 
    Dim newDataStart As Int = sb.Length
    sb.Append(Message).Append(",").Append(SourceID).Append(",").Append(LineNumber).Append(",").Append(Level).Append(Chr(10)).Append("+").Append("*")
    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 i < s.Length - 2 And c = Chr(10) And s.CharAt(i + 1) = "+" And s.CharAt(i + 2) ="*" Then
      
            Dim thisMessage As String = s.SubString2(start, i)
            Dim msgArr() As String = Regex.Split(",", thisMessage) ' Return four components
      
            cm.Message = msgArr(0)
            cm.SourceID = msgArr(1)
            cm.LineNumber = msgArr(2)
            cm.Level = msgArr(3)
      
            i = i + 2
      
'            If SubExists(mModule, mEventName & "_Message") Then
                CallSub2(mModule, mEventName & "_Message", cm)
                If mAutoShow And mLogActive = False Then Show(mDuration) ' Automatically show a console on log messages (inly if hidden)
'            End If
            start = i + 1
      
        End If
    Next
    If start > 0 Then sb.Remove(0, start)
End Sub

Private Sub ChromeClient_Progress (Progress As Int, Title As String, Url As String)
    If SubExists(mModule, mEventName & "_Progress") = False Then Return
 
'    If IsPaused(mModule) Then
'        Do While IsPaused(mModule)
'            Sleep(0)
'        Loop
'    End If
  
    pm.Progress = Progress
    pm.Title = Title
    pm.Url = Url
 
'    If SubExists(mModule, SubName) Then  ' Here or use CallSub and check if caller is paused or use CallSubDelayed that start it before call a sub
'        Log(">>>>> The MAIN sub " & SubName & " exist. Calling it ...")
    CallSubDelayed2(mModule, mEventName & "_Progress", pm) ' Call a function that log the String
'    End If
End Sub

Some relevant code of Main:
Sub Process_Globals
    ' Test HTML
    Dim MyHTML As String = $"
<!DOCTYPE html>
<html>
    <body>
        <center><h1>JS Console test</h1></center>

        <p>Click the button below to increase a counter and show it on the Javascript log.</p>
        <button onclick="Test()">Increase count</button>
  
        <p>Click the button below to reload this page.</p>
        <button onclick="SendToB4A()">Reload the page</button>
  
        <script>
            var cnt = 0;
            function Test() {
               cnt += 1;
               console.log("The number is: [" + cnt + "]");     
            }     
            function SendToB4A() {
               console.log("Call B4A function...")
                B4A.CallSub("myB4Asub",false,"Hello!");
            }
        </script>
    </body>
</html>
"$

End Sub

Sub Globals
    Dim WebView1 As WebView

    Dim Console As JSConsole
    Dim ConsoleHeight As Int
End Sub

Sub Activity_Create(FirstTime As Boolean)
    ConsoleHeight = 34%y
 
    WebView1.Initialize("WebView1")
    Activity.AddView(WebView1, 0, 0, 100%x, 100%y)
 
    Console.Initialize(Activity, Me, "Console", WebView1, ConsoleHeight, 400)
    Console.AddJavascriptInterface("B4A")
 
    WebView1.LoadHtml(MyHTML)
End Sub

Sub WebView1_PageFinished (Url As String)
    Log("Page finished: " & Url)
End Sub

Sub Console_Message(cm As ConsoleMessage) ' Message level can be TIP, LOG, WARNING, ERROR, DEBUG or UNKNOWN (As string)
    If cm.SourceID.StartsWith("file") Then cm.SourceID = "current HTML"
    Console.Console.Text = Console.Console.Text & $"${cm.Level}: ${cm.Message}         (On line ${cm.LineNumber} of ${cm.SourceID})${CRLF}"$
    Log("Received JS console message: " & cm)  ' JS console to B4A console
End Sub

Sub Console_Progress(pm As ProgressMessage)
    Console.Console.Text = Console.Console.Text & $"Progress: ${pm.Progress}%  -  Title: ${pm.Title}  -  URL: ${pm.Url}${CRLF}"$
    Log($"Progress: ${pm.Progress}  -  ${pm.Title}  -  ${pm.Url}"$)
End Sub
 
Sub myB4Asub(Text As String) 'Ignore  THIS CANNOT CALLED HERE, IF I PUT IT ON JSConsole CLASS IT WORKS, BUT I WANT CALL HERE DIRECTLY
    Log("Message received from JS: " & Text)
End Sub
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Please experts some advices ?
 

DonManfred

Expert
Licensed User
Longtime User
Please experts some advices ?
you can not raise an event on another module than the one holding the webview.

You can move the data from the webview-module to anywhere inside the webview-module. Storing a global variable, using callsubdelayed...
 

max123

Well-Known Member
Licensed User
Longtime User
Hi @DonManfred Many thanks for reply, I do not know what data you mean, as data I just load an HTML or web content to a webview.

The same webview exists in all modules and classes, I initialize it in the Main, then pass it to a JSConsole class and this pass it to a WebChromeClient class (java).
JSConsole do not handle it, just add an EditText to the Activity that acts as Console.

JSConsole (in the middle) do not use at all the webview, need it in the Initialize method from the Main just to use it in the WebChromeClient Initialize method, here the class attach a WebChromeClient to the same WebView that is in the Main.

Is like WebViewExtras where you initialize the webview in the Main, then pass to a WebViewExtras that attach WebChromeClient and do some other things.... the Event _Finished is fired in the main, not in other modules.
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
I cannot use BA in Java and access to CallSub method where I can specify the module to call a sub ?

I intend something like this:
Java:
BA.keywords.CallSub2(Main, SubName,  new Object[] {param1});
....or CallSubDelayed and use it instead of ba.raiseEvent.....
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Webview and javascript are related to java in exact 0%
You can use callsubdelayed in the class holding the webview and call a sub in any activity. from your class. You have to implement it by yourself though.
Probably I not explained it good šŸ¤”

The problem is not Javascript at all.
The class that fires the WebView events is not class, is the Main, I initialize the WebView in the Main, then just pass it to add a WebChromeClient. All WebView events are in the Main, _Finished when page is loaded is in the Main.

The JSConsole in middle do not manage the webview, just use it to pass to a WebChromeClient class.

The WebChromeClient wrapper even fires some WebView events that I want to fire in the Main and add to default webview events. It is a Java library developed in Eclipse, I cannot use CallSub as you noticed, is pure Java code, for this reason I asked you if I can use something like B4A.keywords.callSub.

The javascript part only have one problem, because end user probably iinitalize in the Main the JS interface, and cannot access to end class because I compile in the library, so JS callback need to be inside the main. Suppose that the user initialize a JS interface in the Main this way:
B4X:
Console.AddJavascriptInterface("B4A")
... then put a function (myFunction) to be called in JS that way:
JavaScript:
B4A.CallSub(myFunction)
... the function to call must be in the Main.

I hope it is more clear now.
Many thanks
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Possible there is no solution ?
 
Top