kilbee kilbee - 4 months ago 91
Python Question

How to deattach webview once attached?

I've successfully attached WebView to my Kivy app following Kivy wiki instructions. It works as expected, but I'd like to deattach and return to my normal Kivy ui. How to I do that?

I've tried to explore WebView documentation, accessing it's methods (the WebView.destroy() complains about destroying a WebView that's still attached), it's parent methods (I'm not even sure if that's the way to go), but I couldn't get rid of the WebView.

Answer

Ok, I'm not sure whether this is the best solution, or clean enough, but the only one I know that works. While it works and seems stable, it needs further testing by someone with better knowledge of Kivy and Android API itself.

if platform == 'android':
    from jnius import autoclass
    from android.runnable import run_on_ui_thread
    WebView = autoclass('android.webkit.WebView')
    WebViewClient = autoclass('android.webkit.WebViewClient')
    activity = autoclass('org.renpy.android.PythonActivity').mActivity
else:
    import webbrowser
    def run_on_ui_thread(func):
        ''' just for desktop compatibility '''
        return func


class MyScreen(Screen):
    view_cached = None  # make these object properties?
    webview     = None
    wvc         = None  # not even needed probably
    code        = StringProperty() # this property triggers webview to close
    url_to_load = None

    def on_enter(self):
        if platform == 'android':
            Clock.schedule_once(self.create_webview, 0) # probably doesn't need clocked call (because decorators will make sure
                                                        # function runs on correct thread), but leaving it until tested properly
        else:           
            webbrowser.open_new(self.url_to_load)       # on desktop just run the webbrowser

    @run_on_ui_thread
    def on_code(self, *args):
        ''' runs when you are ready to detach WebView '''
        self.detach_webview()

    @run_on_ui_thread
    def create_webview(self, *args):
        ''' attaching webview to app '''
        if self.view_cached is None:
            self.view_cached = activity.currentFocus # caches current view (the one with kivy) as a view we want to go back to; currentFocus or getCurrentFocus() works
        self.webview = WebView(activity)
        settings = self.webview.getSettings()
        settings.setJavaScriptEnabled(True)         # enables js
        settings.setUseWideViewPort(True)           # enables viewport html meta tags
        settings.setLoadWithOverviewMode(True)      # uses viewport
        settings.setSupportZoom(True)               # enables zoom
        settings.setBuiltInZoomControls(True)       # enables zoom controls
        self.wvc = WebViewClient()
        self.webview.setWebViewClient(self.wvc)
        activity.setContentView(self.webview)
        self.webview.loadUrl(self.url_to_load)  

    @run_on_ui_thread
    def key_back_handler(self, *args):
        ''' sketch for captured "key back" event (in App), not tested properly '''
        if self.webview:
            if self.webview.canGoBack() == True:
                self.webview.goBack()
            else:
                self.detach_webview()
                Clock.schedule_once(self.quit_screen, 0)
        else:
            App.get_running_app().root.current = 'some_other_screen_to_switch_to'

    @run_on_ui_thread
    def detach_webview(self, *args):
        if self.webview:
            self.webview.clearHistory()
            self.webview.clearCache(True)
            self.webview.loadUrl("about:blank")
            self.webview.freeMemory()                   # probably not needed anymore
            self.webview.pauseTimers()                  # this should stop any playing content like videos etc. in the background; probably not needed because of 'about:blank' above
            activity.setContentView(self.view_cached)   # sets cached view as an active view
            #self.webview = None # still needs testing;
            #self.wvc = None            

    @mainthread
    def quit_screen(self, *args):
        ''' if not called on @mainthread, it will be freezed '''
        app = App.get_running_app()
        app.root.current = 'some_other_screen_to_switch_to'

I'm creating WebView when entering MyScreen(Screen), and when detaching WebView, switching back to some other Screen.

The view before WebView gets cached (is this efficient? probably would be better to access it some other way) and used again when WebView is destroyed. The quit_screen() calls maybe should be moved to detach_webview(), but the code as a whole probably needs better organization, so leaving it as it is, since this is tested sample.