Search code examples
androidandroid-webviewjavafx-8javafxportsjavafx-webengine

Access to native android.webkit.WebView instance from JavaFXPorts Webview


I want to implement java-to-javascript method call via

WebView.getEngine().executeScript("browserMessagingServicePush('Push data from Java to JS')");

on Android using javafxports 8.60.9, but got

java.lang.UnsupportedOperationException: Not supported yet.
...
at javafx.scene.web.WebEngine.executeScript(WebEngine.java:860)

exception.

How can I get reference to Android's native WebView to implement something like this?

WebView.addJavascriptInterface(new JSInterface(), "JSInterface");

Or there is some other workaround?


Solution

  • Update on Solution

    I found a solution to executing JavaScript using Android native WebView (and not using JavaFX WebView) from a JavaFXPorts application - see code below. I solved the navigation to and from my JavaFX stage and Android WebView (in my 1st answer on 25 February 2018) by using 2 Activities: my main activity (JavaFX Stage) invokes a 2nd Activity (Android WebView) and passes the URL I want to display and when the 2nd Activity finishes it passes back a result string. You can exit the 2nd Activity by executing finish() or using the back button. You need to add an additional entry in the AndroidManifest.xml for the 2nd Activity.

    Here's my main activity:

    import android.content.Intent;
    import javafx.application.Platform;
    import javafxports.android.FXActivity;
    
    /*****************************************
       This is the main activity
    ******************************************/
    public class MainActivity extends FXActivity {
    
        private static final int SECOND_ACTIVITY_REQUEST_CODE = 0;
    
        protected void webView() {
    
            Platform.runLater( () -> {
                    String url ="http://www.example.com";
                    Intent intent = new Intent(FXActivity.getInstance().getApplicationContext(), Test.class);
                    intent.putExtra("url", url);
    
                    // Add result handler
                    FXActivity.getInstance().setOnActivityResultHandler((requestCode, resultCode, data) ->
                        switch (requestCode) {
                            case SECOND_ACTIVITY_REQUEST_CODE:
                                 //if (resultCode == RESULT_OK) {}
    
                                 // get String data from Intent
                                 String result = data.getStringExtra("result");
                                 System.out.println("result = " + result);
                                 break;
    
                            default:                             
                                 super.onActivityResult(requestCode, resultCode, data);
                                 break;
                         }
                    });
    
                    FXActivity.getInstance().startActivityForResult(intent, SECOND_ACTIVITY_REQUEST_CODE);
            });
    
        }
    }
    

    Here's my 2nd Activity (Android WebView):

    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.webkit.ValueCallback;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    
    /*****************************************
        This is the 2nd activity
    ******************************************/
    public class Test extends Activity {
        private String url;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Bundle bundle = getIntent().getExtras();
            if (bundle != null) {
                url = bundle.getString("url");
            }
            WebView webView = new WebView(this);
            setContentView(webView);
            webView.getSettings().setJavaScriptEnabled(true);
            webView.getSettings().setDomStorageEnabled(true);
            webView.setWebViewClient(webClient);
            webView.loadUrl(url);
        }
    
    
        WebViewClient webClient = new WebViewClient() {
    
        @Override
        public void onPageFinished(WebView view, String url) {
             String title = view.getTitle();
             System.out.println("onpagefinished title = "+view.getTitle());
    
             view.evaluateJavascript("(function(){return document.documentElement.outerHTML})();",
                      new ValueCallback<String>() {
    
                          @Override
                          public void onReceiveValue(String html) {
                            System.out.println("evaluateJavascript html = "+html);
    
                            if (<exit 2nd activity condition>) {
                                // put the String to pass back into an Intent and close this activity
                                Intent intent = new Intent();
                                intent.putExtra("result", "this is the result string");
                                setResult(RESULT_OK, intent);
                                finishAndRemoveTask();                  // takes you back to main activity
                            }
                          }
                 });
        }
        };
    
    }
    

    Here's my AndroidManifest.xml which I manually changed to add the 2nd Activity for Test.class:

    <?xml version="1.0" encoding="UTF-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example" android:versionCode="1" android:versionName="1.0">
        <supports-screens android:xlargeScreens="true"/>
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21"/>
        <application android:label="APP" android:name="android.support.multidex.MultiDexApplication" android:icon="@mipmap/ic_launcher">
                <activity android:name="javafxports.android.FXActivity" android:label="Main" android:configChanges="orientation|screenSize">
                        <meta-data android:name="main.class" android:value="com.example.Launcher"/>
                        <meta-data android:name="debug.port" android:value="0"/>
                        <intent-filter>
                                <action android:name="android.intent.action.MAIN"/>
                                <category android:name="android.intent.category.LAUNCHER"/>
                        </intent-filter>
                </activity>
                <activity
                    android:name="com.example.Test"
                    android:label="Test"
                    android:configChanges="orientation|screenSize">
                </activity>
        </application>
    </manifest>
    

    When instantiating MainActivity it crashes with "can't create a handle without a Looper.prepare()". A handle is not explicitly created and so it's not clear which handle is referenced. Solution: create a looper and then after instantiation destroy it. You can only issue Looper.prepare() once otherwise it crashes. Subsequent calls to MainActivity.webView() work without Looper. Here's my code:

    Looper.prepare();
    new MainActivity();
    Looper.myLooper().quitSafely();