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.
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.
Related
It's me again!
Well, that's really strange.
I'using kivy for make an App for Android.
I can use the camera, but or the app resets or do something strange.
Here is the problem:
def chamar_camera(nome,pc,objeto,label_passa,instance):
agora = datetime.now()
nome_arquivo = '%s_%s_%.4i_%.2i_%.2i_%.2i_%.2i_%.2i.jpg' % (nome,pc,agora.year,agora.month,agora.day,agora.hour,agora.minute,agora.second)
# Option 1 - These two lines work:
#def sair():print 'oi'
#camera.take_picture(nome_arquivo, sair)
# Option 2 - These two lines work too:
def sair(label_passa,nome_arquivo):print 'oi'
camera.take_picture(nome_arquivo,on_complete=sair(label_passa,nome_arquivo))
# Option 3 - But these don't:
#def sair(label_passa,nome_arquivo):label_passa.text = nome_arquivo
#camera.take_picture(nome_arquivo, on_complete=sair(label_passa,nome_arquivo))
def on_pause(self):return True
def on_resume(self):pass
On option 3, I write a text (nome_arquivo) on a label widget (label_passa), but what happens is that the text is wrote before the camera be activated. So the camera appear, I can take a picture and the App restarts. I also tried just a "def sair(): pass", but this doesn't work. The only thing working is an "print", but on my app I need to write something in that label and update an sqlite database. Any idea why the function is being called before the camera action?
Thanks!
on_pause and on_resume should be defined as methods of your App class, not (as you have here) functions defined locally within the chamar_camera function.
I am using AndroidViewClient for testing my android application. I need to access name of current focused window. How can I get it?
It's a bit tricky but can be done. The information about the windows and focus is internally maintained by the Views under some conditions.
You may try something like this (choose any view you have):
...
vc = ViewClient(device, serialno, **kwargs2)
vc.dump(window='-1')
no_id1 = vc.findViewByIdOrRaise("id/no_id/1")
no_id1.useUiAutomator = False # this forces getting windows info
no_id1.getXY() # this gets windows info
print no_id1.windows[no_id1.currentFocus]
I would like to create a function which only returns its value once the user input text into a TextInput and click on an ok button. For example
n = PopupInput("What number should I add?")
print 5+n
I can't figure out how to write a kivy dialog which will pause execution and wait until the user closes it. In other GUI toolkits, I would have used something like
while True:
if dialog.visable == False:
return int(textbox.text)
else:
wx.Yield()
To allow my code to just sit in one spot while allowing the GUI framework to do its thing. However, I can find no equivalent method for Kivy.
EDIT:
Here's my unsuccessful attempt (its messy)
def PopupOk(text, title='', btn_text='Continue'):
btnclose = Button(text=btn_text, size_hint_y=None, height='50sp')
content = BoxLayout(orientation='vertical')
p = Popup(title=title, content=content, size=('300dp', '300dp'),
size_hint=(None, None))
content.add_widget(Label(text=text))
ti = TextInput(height='50sp', font_size='50sp', input_type='number')
content.add_widget(ti)
def _on_d(*args):
p.is_visable = False
p.bind(on_dismiss=_on_d)
p.is_visable = True
content.add_widget(btnclose)
btnclose.bind(on_release=p.dismiss)
p.open()
while not p.is_visable:
EventLoop.idle()
return ti.text
I would think about this the other way around - what you really want to do is print the number when the popup is closed.
Assuming you have a popup with a textinput for the user to write in, you can do popup.bind(on_dismiss=some_function) to run that some_function when the popup is closed. That means all you need to do is write a function that takes a popup, retrieves the textbox text, and prints whatever answer you want.
I'm not sure how directly this fits with whatever you're really trying to do, but it's a more natural way to work with Kivy's event system. I can maybe answer differently if you have some strongly different requirement.
Edit: Seeing your edit, this is almost what you do, but I think it is a bad idea to try and beat the eventloop into submission this way rather than going with the flow. I would create a new function (as I said above) that takes a textinput and does whatever you really want. By binding on_dismiss to this function, you let kivy take care of starting your computation later whenever the user gets around to dismissing the popup.
Kivy is really built around the principle of events and async callbacks. Because it uses OpenGL and relies upon frames rendered on the GPU, not the CPU, you never want to use blocking code. So kivy uses event binding to circumvent the issue.
Here is one approach.
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MainApp(App):
def build(self):
self.button = Button(text="Click",
on_release=self.get_caption)
return self.button
def get_caption(self, btn):
Popup(title="Enter text here",
content=TextInput(focus=True),
size_hint=(0.6, 0.6),
on_dismiss=self.set_caption).open()
def set_caption(self, popup):
self.button.text = popup.content.text
MainApp().run()
You place content in a popup, when give it a "set_caption" function to call when it's dismissed. There you respond to the change. No blocking. No waiting. Having worked with threading to stop GUI blocking in wxWidgets, I really think this is a better way...;-)
Cheers
What you are looking for can be achieved with the following code. You need to specify routines as methods in the main program:
import kivy
kivy.require('1.5.0') # replace with your current kivy version !
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.clock import Clock
class YourApp(App):
def build(self):
return Button(text='Press for popup!', on_press=self.callpopup)
def callpopup(self, event):
dlg = MessageBox(titleheader="Titel Header", message="Any Message", options={"YES": "printyes()", "NO": "printno()"})
print "Messagebox shows as kivy popup and we wait for the \nuser action and callback to go to either routine"
def printyes(self):
# routine for going yes
print "You chose the Yes routine"
def printno(self):
# routine for going no
print "You chose the No routine"
class MessageBox(YourApp):
def __init__(self, titleheader="Title", message="Message", options={"OK": "self.ok()", "NO": "self.no()"}):
def popup_callback(instance):
"callback for button press"
# print('Popup returns:', instance.text)
self.retvalue = instance.text
self.popup.dismiss()
self.retvalue = None
self.options = options
box = BoxLayout(orientation='vertical')
box.add_widget(Label(text=message, font_size=20))
b_list = []
buttonbox = BoxLayout(orientation='horizontal')
for b in options:
b_list.append(Button(text=b, size_hint=(1,.35), font_size=20))
b_list[-1].bind(on_press=popup_callback)
buttonbox.add_widget(b_list[-1])
box.add_widget(buttonbox)
self.popup = Popup(title=titleheader, content=box, size_hint=(None, None), size=(400, 400))
self.popup.open()
self.popup.bind(on_dismiss=self.OnClose)
def OnClose(self, event):
self.popup.unbind(on_dismiss=self.OnClose)
self.popup.dismiss()
if self.retvalue != None:
command = "super(MessageBox, self)."+self.options[self.retvalue]
# print "command", command
exec command
if __name__ == '__main__':
YourApp().run()
in Kivy, when I press the back button on my android device it throws me out of the application. is there a way to return back to the previous screen using the Kivy language and not python? this is what I have written in kivy:
<MyAppClass>:
AnchorLayout:
anchor_x : 'center'
anchor_y : 'top'
ScreenManager:
size_hint : 1, .9
id: _screen_manager
Screen:
name:'screen1'
StackLayout:
# irrelevant code
Screen:
name:'screen2'
StackLayout:
# irrelevant code
I need to manipulate the screen manager and its screens from python... if I can do so I will be ok with python.
Kivy on android binds the back button to the esc button so binding and listening to esc button in your app would help you handle how your app behaves when the back button is pressed.
In other words in your app when testing it on your desktop listen to the escape key from the system keyboard, this will be automatically be translated to being the back button on your android device. Something like::
def on_start():
from kivy.base import EventLoop
EventLoop.window.bind(on_keyboard=self.hook_keyboard)
def hook_keyboard(self, window, key, *largs):
if key == 27:
# do what you want, return True for stopping the propagation
return True
i guess that i have solved it but should thank both #inclement and #qua-non! your answers guys led me to the right way! so in kv i assume that i gave an id to my screen manager (please refer to my question where i have written the kv code) , in python i should do the following:
from kivy.core.window import Window
from kivy.properties import ObjectProperty
class MyAppClass(FloatLayout):#its a FloatLayout in my case
_screen_manager=ObjectProperty(None)
def __init__(self,**kwargs):
super(MyAppClass,self).__init__(**kwargs)
#code goes here and add:
Window.bind(on_keyboard=self.Android_back_click)
def Android_back_click(self,window,key,*largs):
if key == 27:
self._scree_manager.current='screen1'#you can create a method here to cache in a list the number of screens and then pop the last visited screen.
return True
class MyApp(App):
def build(self):
return MyAppClass()
if __name__=='__main__':
MyApp().run()
This is certainly possible. Here's a short example app with the method I use to do this:
from kivy.utils import platform
from kivy.core.window import Window
class ExampleApp(App):
manager = ObjectProperty()
def build(self):
sm = MyScreenManager()
self.manager = sm
self.bind(on_start=self.post_build_init)
return sm
def post_build_init(self, *args):
if platform() == 'android':
import android
android.map_key(android.KEYCODE_BACK, 1001)
win = Window
win.bind(on_keyboard=self.my_key_handler)
def my_key_handler(self, window, keycode1, keycode2, text, modifiers):
if keycode1 in [27, 1001]:
self.manager.go_back()
return True
return False
This should give the right basic idea, but a few notes:
ScreenManager doesn't keep track of the previous screens, it's up to you to implement this how you like. My example assumes you defined a class MyScreenManager with a go_back method.
It might not be necessary to bind to on_start and run post_build_init, this is just how the example I originally used did it (see below). It might be important sometimes though, possibly if the window is not initialised when build() is run, and the original mailing list post suggests the author needed it for some reason.
The example listens for keycodes 27 or 1001. As qua-non said while I was writing this, the former listens for esc, so you can get the same behaviour on desktop.
I didn't try without the android.map_key line, but it seems like it may not be necessary.
You mention you want to use kivy language and not python. You need to do some python to get this result, and I don't see a way around that (it's not really the domain of the kv language). I guess you could shift some stuff to kv by defining a 'go_back' event somewhere and triggering this when the key is pressed, along with binding your screenmanager to watch that event, but it seems like a long way around.
I based my code on the mailing list thread at https://groups.google.com/forum/#!topic/kivy-users/7rOZGMMIFXI . There might be a better way, but this is quite functional.
Now all the way in 2020 I'm using:
Clock.schedule_once(lambda x: Window.bind(on_keyboard=self.hook_keyboard))
in combination with a similar hook_keyboard method to the other answers, to delay the bind in my build method. Works fine, but none of these other ways ways seemed to work for me anymore.
Please i am looking for a work around to get access Android camera through kivy, or a library that i can integrate with kivy in order to access the Camera.
I am developing an application for android but using python-kivy for the UI,
anything will be really appreciated,
thanks alot.
Here's my sample code, that works on Android. Just import that file https://github.com/kivy/plyer/blob/master/plyer/platforms/android/camera.py
Also, don't forget to add CAMERA permissions to manifest.
main.py:
__version__ = '1.0'
import kivy
# importing file from https://github.com/kivy/plyer/blob/master/plyer/platforms/android/camera.py
# I downloaded it and saved it in the same directory:
from camera import AndroidCamera
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, StringProperty
import base64
class MyCamera(AndroidCamera):
pass
class BoxLayoutW(BoxLayout):
my_camera = ObjectProperty(None)
# /sdcard means internal mobile storage for that case:
image_path = StringProperty('/sdcard/my_test_photo.png')
def __init__(self, **kwargs):
super(BoxLayoutW, self).__init__()
self.my_camera = MyCamera()
def take_shot(self):
self.my_camera._take_picture(self.on_success_shot, self.image_path)
def on_success_shot(self, loaded_image_path):
# converting saved image to a base64 string:
image_str = self.image_convert_base64
return True
#converting image to a base64, if you want to send it, for example, via POST:
def image_convert_base64(self):
with open(self.image_path, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read())
if not encoded_string:
encoded_string = ''
return encoded_string
if __name__ == '__main__':
class CameraApp(App):
def build(self):
main_window = BoxLayoutW()
return main_window
CameraApp().run()
camera.kv:
<BoxLayoutW>:
Button:
text: 'shot'
on_release: root.take_shot()
Kivy has some native support for calling the camera. Check out this page from the new programming guide for a core provider or this page from the new programming guide for a uix widget. In theory, the core should be able to adapt between platforms and the widget should then be able to use the camera.
This links to a discution where a custom implementation can be found. It is based on PyJNIus's automatic wrapping of the android API's Camera class.
Didn't try myself but you can give it a try...
thanks to this post i was able to solve a critical problem in my app thanks a lot guys here is my code that i used i hope that you guys can use it somewhere.
I made a screen and used the plyer camera function
from os import getcwd
from os.path import exists
from os.path import splitext
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup
from kivy.logger import Logger
from plyer import camera
i also used some other imports for the screens and the labels and popups etc which you can definatly look into depending upon your requirment
class ScreenFive(Screen): #camera screen
def gg1back(self):
self.parent.current = 'First'
def do_capture(self):
filepath = 'IMG_1.jpg'
ext = splitext(filepath)[-1].lower()
try:
camera.take_picture(self.camera_callback,filepath)
except NotImplementedError:
popup = MsgPopup(
"The Face_rec_image feature has not yet \n been implemented for this platform :(")
popup.open()
def camera_callback(self, filepath):
if(exists(filepath)):
popup = MsgPopup("Picture saved!")
popup.open()
else:
popup = MsgPopup("Could not save your picture!")
popup.open()
As it was hard for me to find the answer how to use camera on android I thought I'll share my journey to the answer to save next person's time.
I couldn't find the way to make work Camera class straight from Kivy:
https://kivy.org/docs/examples/gen__camera__main__py.html
finally I found the solution posted above, and after wasting some time implementing it in my app it turned out it was impossible for me to return to the app after photo being taken - the app was terminated, so I couldn't go back to the app to make use of the picture (I was using Kivy Launcher).
Just recently I found out this way of accessing Camera was abandoned (https://github.com/kivy/plyer/issues/16#issuecomment-54094174 )
But then I found the solution below and by just running the example code it looks like I will be able to get results I want (it just needs a little tweaking not to crash when android camera is canceled/no photo has been taken)
https://github.com/kivy/kivy/tree/master/examples/android/takepicture
EDIT:
appears my app was terminated because I didn't implement on_pause: return True in topmost widget. Nevertheless the text above still might be helpful
Some years later, the Android API has changed as to how it deals with permissions and storage providers.
I have a full working example for the Android camera through Kivy here. It basically requires some tweaking of the compiled manifest file in python-for-android, as well as working directly with the FileProvider.