0

The WebView Code

from kivy.uix.modalview import ModalView
from kivy.clock import Clock
from android.runnable import run_on_ui_thread
from jnius import autoclass, cast, PythonJavaClass, java_method

WebViewA = autoclass('android.webkit.WebView')
WebViewClient = autoclass('android.webkit.WebViewClient')
LayoutParams = autoclass('android.view.ViewGroup$LayoutParams')
LinearLayout = autoclass('android.widget.LinearLayout')
KeyEvent = autoclass('android.view.KeyEvent')
ViewGroup = autoclass('android.view.ViewGroup')
DownloadManager = autoclass('android.app.DownloadManager')
DownloadManagerRequest = autoclass('android.app.DownloadManager$Request')
Uri = autoclass('android.net.Uri')
Environment = autoclass('android.os.Environment')
Context = autoclass('android.content.Context')
PythonActivity = autoclass('org.kivy.android.PythonActivity')


class DownloadListener(PythonJavaClass):
    #https://stackoverflow.com/questions/10069050/download-file-inside-webview
    __javacontext__ = 'app'
    __javainterfaces__ = ['android/webkit/DownloadListener']

    @java_method('(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V')
    def onDownloadStart(self, url, userAgent, contentDisposition, mimetype,
                        contentLength):
        mActivity = PythonActivity.mActivity 
        context =  mActivity.getApplicationContext()
        visibility = DownloadManagerRequest.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
        dir_type = Environment.DIRECTORY_DOWNLOADS
        uri = Uri.parse(url)
        filepath = uri.getLastPathSegment()
        request = DownloadManagerRequest(uri)
        request.setNotificationVisibility(visibility)
        request.setDestinationInExternalFilesDir(context,dir_type, filepath)
        dm = cast(DownloadManager,
                  mActivity.getSystemService(Context.DOWNLOAD_SERVICE))
        dm.enqueue(request)


class KeyListener(PythonJavaClass):
    __javacontext__ = 'app'
    __javainterfaces__ = ['android/view/View$OnKeyListener']

    def __init__(self, listener):
        super().__init__()
        self.listener = listener

    @java_method('(Landroid/view/View;ILandroid/view/KeyEvent;)Z')
    def onKey(self, v, key_code, event):
        if event.getAction() == KeyEvent.ACTION_DOWN and\
           key_code == KeyEvent.KEYCODE_BACK: 
            return self.listener()
        

class WebView(ModalView):
    # https://developer.android.com/reference/android/webkit/WebView
    
    def __init__(self, url, enable_javascript = False, enable_downloads = False,
                 enable_zoom = False, width=None, height=None, **kwargs):
        super().__init__(**kwargs)
        self.url = url
        self.enable_javascript = enable_javascript
        self.enable_downloads = enable_downloads
        self.enable_zoom = enable_zoom
        self.webview = None
        self.enable_dismiss = True
        if width:
            self.width = width
        if height:
            self.height = height
        self.open()

    @run_on_ui_thread        
    def on_open(self):
        mActivity = PythonActivity.mActivity 
        webview = WebViewA(mActivity)
        webview.setWebViewClient(WebViewClient())
        webview.getSettings().setJavaScriptEnabled(self.enable_javascript)
        webview.getSettings().setBuiltInZoomControls(self.enable_zoom)
        webview.getSettings().setDisplayZoomControls(False)
        webview.getSettings().setAllowFileAccess(True) #default False api>29
        layout = LinearLayout(mActivity)
        layout.setOrientation(LinearLayout.VERTICAL)
        layout.addView(webview, self.width, self.height)
        mActivity.addContentView(layout, LayoutParams(-1,-1))
        webview.setOnKeyListener(KeyListener(self._back_pressed))
        if self.enable_downloads:
            webview.setDownloadListener(DownloadListener())
        self.webview = webview
        self.layout = layout
        try:
            webview.loadUrl(self.url)
        except Exception as e:            
            print('Webview.on_open(): ' + str(e))
            self.dismiss()  
        
    @run_on_ui_thread        
    def on_dismiss(self):
        if self.enable_dismiss:
            self.enable_dismiss = False
            parent = cast(ViewGroup, self.layout.getParent())
            if parent is not None: parent.removeView(self.layout)
            self.webview.clearHistory()
            self.webview.clearCache(True)
            self.webview.clearFormData()
            self.webview.destroy()
            self.layout = None
            self.webview = None
        
    @run_on_ui_thread
    def on_size(self, instance, size):
        if self.webview:
            params = self.webview.getLayoutParams()
            params.width = self.width
            params.height = self.height
            self.webview.setLayoutParams(params)

    def pause(self):
        if self.webview:
            self.webview.pauseTimers()
            self.webview.onPause()

    def resume(self):
        if self.webview:
            self.webview.onResume()       
            self.webview.resumeTimers()

    def downloads_directory(self):
        # e.g. Android/data/org.test.myapp/files/Download
        dir_type = Environment.DIRECTORY_DOWNLOADS
        context =  PythonActivity.mActivity.getApplicationContext()
        directory = context.getExternalFilesDir(dir_type)
        return str(directory.getPath())

    def _back_pressed(self):
        if self.webview.canGoBack():
            self.webview.goBack()
        else:
            self.dismiss()  
        return True
    
    def closeWebView(self, force=False, animation=True):
        self.dismiss(force=force, animation=animation)

Display WebView

class App(APP):
    def build(self):
        #self.manager = ScreenManager()
        #login = Screen(name="login")
        #login.add_widget(LoginPage())
        #register = Screen(name="register")
        #register.add_widget(Register())
        #forgotLogin = Screen(name="forgot")
        #forgotLogin.add_widget(ForgotLogin())
        #chat = Screen(name="chat")
        #chat.add_widget(Chat())
        #self.manager.add_widget(login)
        #self.manager.add_widget(register)
        #self.manager.add_widget(forgotLogin)
        #self.manager.add_widget(chat)
        #self.manager.current = "login"
        return WebView(url="https://google.com/", enable_javascript=True, enable_zoom=True)

if __name__== "__main__":
   chat = App()
   chat.run()

I compiled and tested on android 10 it force closes and after running adb logcat I get:

05-29 23:37:14.410  1760  4712 D PowerManagerServiceEx: releaseWakeLockInternal: lock=42843022 [WindowManager], flags=0x0
05-29 23:37:14.469 27595 27630 I python  :  Traceback (most recent call last):
05-29 23:37:14.469 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/app/main.py", line 66, in <module>05-29 23:37:14.469 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/ga.animechat.chatapp/kivy/app.py", line 949, in run
05-29 23:37:14.470 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/ga.animechat.chatapp/kivy/app.py", line 919, in _run_prepare
05-29 23:37:14.470 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/app/main.py", line 26, in build
05-29 23:37:14.470 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/app/browse.py", line 98, in __init__
05-29 23:37:14.470 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/ga.animechat.chatapp/kivy/uix/modalview.py", line 222, in open
05-29 23:37:14.470 27595 27630 I python  :    File "/home/mike/kiv/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/ga.animechat.chatapp/kivy/core/window/__init__.py", line 1305, in add_widget
05-29 23:37:14.470 27595 27630 I python  :  kivy.uix.widget.WidgetException: Cannot add <browse.WebView object at 0xbedc50d0> to window, it already has a parent <__main__.App object at 0xbf7f1258>
05-29 23:37:14.470 27595 27630 I python  : Python for android ended.
05-29 23:37:14.521 27595 27622 E libEGL  : validate_display:91 error 3008 (EGL_BAD_DISPLAY)
05-29 23:37:14.521 27595 27622 I chatty  : uid=10479(ga.animechat.chatapp.ga.animechat.chatapp) imechat.chatapp identical 3 lines
05-29 23:37:14.521 27595 27622 E libEGL  : validate_display:91 error 3008 (EGL_BAD_DISPLAY)
05-29 23:37:14.521 27595 27622 F OpenGLRenderer: Failed to set damage region on surface 0xe6e60e20, error=EGL_BAD_DISPLAY
05-29 23:37:14.521 27595 27622 F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 27622 (RenderThread), pid 27595 (imechat.chatapp)

I Don't know what to do I tried passing parent=self it still gave the error. Any idea on what to do? Google doesn't mention the webview anywhere so hopefully you can point out a way to fix it. On the actual code the webview is in its seperate file not sure if that's the issue or not. Maybe I should wrap it in another class that inherits Widget.

Michael
  • 43
  • 3

1 Answers1

0

The problem is that the Webview (ModalView) assigns its own parent. This is a feature of the ModalView. By returning a Webview instance in your build() method, you are attempting to set the App as the parent of the Webview. That causes the error you are seeing. You can solve this by returning something other than a Webview:

def build(self):
    WebView()
    return None
John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • is it possible to use it with screen manager then return self.manager? – Michael May 30 '21 at 13:53
  • You cannot add a `WebView` as a child of any widget. If you want to use your `WebView` as the root of your `App`, perhaps you should change `class WebView(ModalView):` to `class WebView(AnchorLayout):`. – John Anderson May 30 '21 at 14:02
  • Perhaps you could define `WebView` as `class WebView(Screen)`. Then your `on_open()` could be replaced by `on_enter()`, and `on_dismiss()` could be replaced by `on_leave()`. – John Anderson May 30 '21 at 14:13