>>> a = 1 >>> b = 2 >>> print(a) 1 >>> print(b) 2 >>> print(a+b) 3 >>> print(b-a) 1 >>> print(a<b) True]]>
I/python (11009): File “[]/android_zbar_qrcode_master/.buildozer/android/app/main.py”, line 170, in start
I/python (11009): File “jnius_export_class.pxi”, line 830, in jnius.jnius.JavaMultipleMethod.__call__ (jnius/jnius.c:21088)
I/python (11009): File “jnius_export_class.pxi”, line 561, in jnius.jnius.JavaMethod.__call__ (jnius/jnius.c:17854)
I/python (11009): File “jnius_export_class.pxi”, line 727, in jnius.jnius.JavaMethod.call_staticmethod (jnius/jnius.c:19696)
I/python (11009): File “jnius_utils.pxi”, line 43, in jnius.jnius.check_exception (jnius/jnius.c:3233)
I/python (11009): JavaException: JVM exception occured
Which I experienced when, last week, I added the INTERNET permission to the spec file, but mistyped a . for a ,
I forgot about it and came back to it today and nothing worked – despite me not being able to see any substantive difference between my current py file and the known good one.
]]>
There are a couple of tricks to it.
The process is (install the app engine sdk and copy of web2py):
1. create your application on your app engine developer Application Overview page
2. create an application skeleton using the web2py dashboard.
3. cp examples/appengine_config.example.py ./app.yaml
Ie make a copy in the root directory of your web2py directory tree. This is main trick #1.
4. Edit app.yaml to refer to the id that you got from creating the app on app engine (ie not the local web2py)
5. cp handlers/gaehandler.py .
So, copy the gaehandler to the root directory of web2py. This is main trick #2.
Then you should be ready to roll*
* use <path>/dev_appserver.py web2py to test,
use <path>/appcfg.py update web2py to deploy
The app will be available from <the Google app id you registered>.appspot.com/<the local web2py name of your application>/
]]>
…or it would mean that if Kivy didn’t defocus the textinput widget whenever it calls on_text_validate. After each can the widget needs to be refocussed. The widget can be refocussed by setting it’s .focus attribute to True. The trick however is to make sure that happens _after_ Kivy has defocussed the widget. To do that you need to schedule a callback to occur after the next Kivy “frame”:
Clock.schedule_once(self._refocus_text_input, 0)
#from kivy.clock import Clock
def _refocus_text_input(self, arg1):
self.scan_entry.focus=True
Awesome. Now I don’t have to tap the widget to reset it after each scan.
]]> Intent = autoclass('android.content.Intent')
intent = Intent()
intent.setAction("com.google.zxing.client.android.SCAN")
Logger.debug("build: about to start activity for result")
PythonActivity.mActivity.startActivityForResult(intent, 0x123)
It goes without saying that you need to have ZXing already installed on the device. Apparently it will also work with other scanners, but I haven’t tried.
You also need to have a callback to handle the returned result:
def on_activity_result(self, requestCode, resultCode, data):
isbn = data.getStringExtra("SCAN_RESULT")
# do other stuff...
And this callback must first be bound:
activity.bind(on_new_intent=self.on_new_intent,on_activity_result=self.on_activity_result) # earlier: from android import activity]]>
The problem with using on_new_intent in an app is that on_resume() and on_new_intent() are both fired at more or less the same time. There is no guarantee that one of them will be called first and no guarantee that the one called first will return before the second one is called (in my experiments they can fire in either order and can fire in the middle of the other). In other contexts this might not be a problem. Here it is. The purpose of on_resume is to reinitialise the App state to where it was as at the last call to on_pause(). The purpose of on_new_intent is to initialise or update the App state based on the data provided by the intent. This is not a problem for services because services are running in the background and on_resume need not be called.
The future solution is that I’ve logged an issue. My current solution is a hackish workaround – delay the execution of on_new_intent to give on_resume a chance to start, and use flags for on_new_intent to wait for it to finish. If the timing is bad, this will fail, but seems to be ok in practice so far (ie last 24 hours).
Sample code (init the attributes to False when you instantiate the app too btw):
class GridderApp(App):
#[stuff deleted]
def on_resume(self):
if self.on_new_intenting: # let it run, don't bother about initialising to saved
return
self.resuming = True
self.uiwidget.load(path=".", filename=PAUSE_FILE_NAME, store_save= False)
self.resuming = False
def on_new_intent(self, intent):
self.on_new_intenting = True
sleep(0.25) # give on_resume a chance to fire
# if it fires after on_new_intent has finished, it will reinitialise to the saved state
# and we want to avoid that!
while self.resuming:
sleep(0.1)
intent_data = intent.getData()
try:
file_uri= intent_data.toString()
except AttributeError:
file_uri = None
if file_uri is None:
return
else:
if file_uri[:7]=="file://":
self.uiwidget.load(path="", filename=url2pathname(file_uri[7:]))
else:
return
self.on_new_intenting= False
]]>class GridderApp(App):
# [stuff deleted]
def on_new_intent(self, intent):
intent_data = intent.getData()
try:
file_uri= intent_data.toString() # isn't Java awesome?
except AttributeError:
file_uri = None
if file_uri is None:
return # give up
else:
if file_uri[:7]=="file://":
self.uiwidget.load(path="", filename=url2pathname(file_uri[7:]))
else:
return # again, give up
That’d all be lovely if that’s how it actually worked. Unfortunately on_new_intent seems to be designed on the assumption that it’s being run as the method of a Service not an app. This code won’t work for an app for reasons to be covered in a later post.
After you’ve defined this method you need to bind it:
if __name__ == "__main__":
if platform=="android":
import android.activity
app = GridderApp(file_uri=file_uri)
android.activity.bind(on_new_intent=app.on_new_intent)
After which, you’re good to go! Except that apps suffer from a problem with receiving intents – see my next post.
Apparently (as at July 2014) on_new_intent does not work on the application’s first start, so you can’t get (eg) a file uri from an intent if your app has not been run before or has been run, but stopped. I’m told this is a bug so it may be fixed in the future.
]]>
Android deals with this primarily through calling on_pause and on_resume methods on apps (actually onPause and onResume in Java). If the app wants to do something there (eg save/restore current data!), well and good. Android calls on_pause() when it returns Android suspends the app until someone brings the app to the foreground again, at which time on_resume() is called. The Android lifecycle is actually more complicated than this but let’s not sweat the details.
Kivy allows you to implement the on_pause and on_resume simply by adding these methods to your app instance. Some sample code:
class GridderApp(App):
# stuff deleted
def on_pause(self):
self.uiwidget.save(path =".", filename=PAUSE_FILE_NAME)
return True # app sleeps until resume return False to stop the app
def on_stop(self):
pass
def on_resume(self):
self.uiwidget.load(path=".", filename=PAUSE_FILE_NAME, store_save= False)
]]>
In order to make use of these intents from Kivy/buildozer you need to add an intent-filter to your a file (by default called intent_filters.xml) in your app’s root directory. You also need to edit the buildozer.spec file to point to the file (look for this line and uncomment):
# (str) XML file to include as an intent filters in <activity> tag
android.manifest.intent_filters=intent_filters.xml
The intent filter will differ depending on the intent. Intents based on mimetypes are better than those based on paths. Apparently there’s a problem with Android’s file search algorithm so you need to specify every relevant directory level if you want to filter on the file’s extension (seriously!). Sample intent (from my art gridder app, which uses files with a .bag extension:
<intent-filter>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”*/*” />
<data android:scheme=”file” />
<data android:host=”*” />
<data android:port=”*” />
<data android:pathPattern=”.*..*..*..*..*.bag” />
<data android:pathPattern=”.*..*..*..*.bag” />
<data android:pathPattern=”.*..*..*.bag” />
<data android:pathPattern=”.*..*.bag” />
<data android:pathPattern=”.*.bag” />
</intent-filter>
Finally, your app needs to be able to receive and extract the intent (don’t expect to find this in the docs!):
if __name__ == "__main__":
if platform=="android":
from jnius import cast
from jnius import autoclass
import android
import android.activity
# test for an intent passed to us
PythonActivity = autoclass('org.renpy.android.PythonActivity')
activity = PythonActivity.mActivity
intent = activity.getIntent()
intent_data = intent.getData()
try:
file_uri= intent_data.toString()
except AttributeError:
file_uri = None
Then pass the file uri to the app when you instantiate it.
]]>Example, from the kivy site:
import kivy
kivy.require('1.0.6') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text='Hello world')
if __name__ == '__main__':
MyApp().run()
Then run python main.py…
]]>