0

Although there are examples of using WebView to interact with html web forms that require JavaScript, this question is about doing all the interaction between the web site and the Android application in the background. Although AsyncTask or other techniques are possible, this question is about using the more recently introduced WorkManager. Another possibility for interacting with a web page in the background would be Selenium-HtmlUnit, but there are problems there, so I'm asking about WebView.

For an Android application that requires interaction with a web site (that uses JavaScript), hidden from the end user and not consuming/blocking the UI thread, what programming technique is required to have a WebView operate in a Worker.doWork() method?

The problem I encountered was that WebView is designed to run in an Activity, I guess because it's a View. So tried to force the issue by creating the web view in onCreate() and saving it for later use (yes, not good practice):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    WebView webView = findViewById(R.id.web_view);

    // **** Save WebView in Application class *********//
    MyApplication.saveWebView(webView); //MyApplication extends Application webView is a static variable there.  Yes, it's not the right way!

    Data formInputs = new Data.Builder()
            .putString("inputA", "some input")
            .putString("inputB", "some other input")
            .build();

    OneTimeWorkRequest aRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
            .setInputData(formInputs)
            .addTag("myRequestTag")
            .build();

    WorkManager.getInstance(this).beginWith(aRequest)
            //.then(anotherRequest) // not shown, but it's critical it runs AFTER the first request.
            .enqueue();

}

Then I pull the WebView out when I'm in the Worker.doWork() method:

public Result doWork() {
    Data inputData = getInputData();
    String inputA = inputData.getString("InputA");
    String inputB = inputData.getString("InputB");
    String urlString = "https://www.somewebsite.com/forums/some.php?do=someform";
    Log.d(TAG, "doWork: LogonWorker: About to try form submit for " + inputA);

    WebView myWebView = MyApplication.getWebView();

    myWebView.post(() -> setupWebView(myWebView)); // OK to run on the main thread since it's short running.  Must run on main thread or throws WebView 'same thread' error.
    
    boolean keepItInTheBackground = true;
    if (keepItInTheBackground) {
        myWebView.loadUrl(urlString); // true: ERROR THROWN HERE
    } else {
        myWebView.post(() -> myWebView.loadUrl(urlString)); // false: WORKS, BUT GOES TO NEXT WORKER IMMEDIATELY (without waiting for this first page to process).
    }

    ... build of outputData not shown (not important to this question)
    
    Result result = Result.success(outputData); // becomes the input to the next process
    return result;
}

This is the error if loadUrl() runs in the worker thread:

A WebView method was called on thread 'androidx.work-1'. All WebView methods must be called on the same thread.

And if loadUrl() is called as a Runnable, the doWork() returns before the results of the network operation, which is required for the follow-on WorkManager request.

So the question: what is the proper programming technique to get the time-consuming task (network page load) to be managed in the background and so a sequence of tasks can be processed one at a time, so the input from earlier tasks are available to later ones. This approach may be ill-advised or "impossible", and if that's the answer then so be it.

For completeness, this is the method I call to set up the web view, that I just copied from another SO question. This is a short running task, so don't mind it as a Runnable inside the Worker:

private void setupWebView(WebView webView) {
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new WebViewClient());
    webView.getSettings().setLoadWithOverviewMode(true);
    webView.getSettings().setUseWideViewPort(true);
    webView.getSettings().setSupportZoom(false);
    webView.getSettings().setBuiltInZoomControls(false);
    webView.getSettings().setDisplayZoomControls(false);
    webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
    webView.requestFocusFromTouch();
    webView.getSettings().setAppCacheEnabled(false);
    webView.setScrollbarFadingEnabled(false);
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            String cookies = CookieManager.getInstance().getCookie(url);
            Log.d(TAG, "onPageFinished: cookies: " + cookies);
            StringRequest Request = new StringRequest(url,
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
                            Log.d(TAG, "onResponse: onPageFinished: " + response.toString());
                            // your data from server
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            Log.d(TAG, "onErrorResponse: onPageFinished: " + error.toString());
                        }
                    })
            {
                @Override
                public Map<String, String> getHeaders() throws AuthFailureError {
                    Map<String, String> map = new HashMap<>();
                    map.put("Cookie", cookies);
                    Log.d(TAG, "getHeaders: cookies: " + cookies);
                    return map;
                }
            };
        }
    });
Dale
  • 4,966
  • 4
  • 39
  • 75

0 Answers0