Closed mhsmith closed 2 years ago
I've found one more Rubicon API we'll need to emulate: __null__
in toga_android/widgets/progressbar.py. This is easily emulated in terms of Chaquopy's cast
, although Chaquopy would also accept a simple None
in this context.
@freakboy3742: Since Rubicon's public API isn't documented anywhere, I'm not sure if I've covered everything. Here's what I've got so far:
JavaClass
JavaInterface
__global__
__null__
_alternates
Can you think of anything else?
EDIT: I've found another one, __cast__
: not currently used by Toga, but might be used in user code.
Performance comparisons:
Rubicon | Chaquopy | |
---|---|---|
Startup after clearing data (seconds) | 8.21 | 4.07 |
Startup with existing data (seconds) | 3.05 | 3.79 |
"App size" (MB) | 35.64 | 29.99 |
"User data" (MB) | 28.80 | 7.76 |
Review of open rubicon-java issues:
https://github.com/beeware/rubicon-java/issues/70
https://github.com/beeware/rubicon-java/issues/54
https://github.com/beeware/rubicon-java/issues/43
https://github.com/beeware/rubicon-java/issues/39
isinstance
and issubclass
work with Java objects and classes in the normal way.https://github.com/beeware/rubicon-java/issues/25
I've found one more Rubicon API we'll need to emulate:
__null__
in toga_android/widgets/progressbar.py. This is easily emulated in terms of Chaquopy'scast
, although Chaquopy would also accept a simpleNone
in this context.
The current state of None
/__null__
handling was a subject of some discussion; it was introduced fairly recently (earlier this year) as an "explicit is better than implicit" approach to resolving which polymorphic method would be selected when passing None as an argument. It's a bit of a wart, but one that I felt was necessary to avoid vastly more complex code in the process of invoking methods. If Chaquopy has a more robust solution here, I'm not opposed to dropping this.
@freakboy3742: Since Rubicon's public API isn't documented anywhere, I'm not sure if I've covered everything. Here's what I've got so far:
JavaClass
JavaInterface
__global__
__null__
_alternates
Can you think of anything else?
EDIT: I've found another one,
__cast__
: not currently used by Toga, but might be used in user code.
I would definitely expect to see __cast__
in user code; again, it's a necessary evil required by the polymorph selection code, added earlier this year. I think you're right that it's not currently used in Toga, but we do use the thing that preceded __cast__
- direct access to the __jni__
attribute. This is a bit of a hack that allows Python to pass around the "raw" JNI object rather than wrapping it.
Other than that, I can't think of anything obviously missing from your list.
The performance numbers you've provided are very favourable. I'm intrigued what makes Rubicon marginally faster in the "2+ run" case; I'm guessing it's the space+first start tradeoff? (i.e., Chaquopy doesn't unpack everything, which means it's faster on first run and uses less space, but every other run still needs to do in-memory unpacking which isn't as fast as direct access?)
The Rubicon bugs you've highlighted, though, definitely make Chaquopy an attractive solution. Exception handling is critical for working out when things crash - I'm guessing the bug we've had reported about asycnio/threading crashes is proving difficult to hunt because the stack trace is being eaten by the JNI layer of Rubicon. Subclassing and isinstance
handling is also something that will absolutely be required to implement any of the more interesting Android APIs, so we're going to need a solution; if Chaquopy has one out of the box, that is a huge plus.
One thing that would be interesting to see prototyped is whether we can remove DrawHandlerView
and IDrawHandler
from the template. Those classes exist entirely to support the Canvas widget due to the lack of subclassing support in Rubicon; removing those classes in favour of a "native" Chaquopy subclass would be a good capability demonstrator.
The current state of
None
/__null__
handling was a subject of some discussion; it was introduced fairly recently (earlier this year) as an "explicit is better than implicit" approach to resolving which polymorphic method would be selected when passing None as an argument.
Chaquopy accepts None
when there's no ambiguity. If there is ambiguity it throws a TypeError, and you must resolve it with cast(ClassName, None)
, so I'll add that to the emulation layer as the implementation of __null__
.
I would definitely expect to see
__cast__
in user code; again, it's a necessary evil required by the polymorph selection code, added earlier this year. I think you're right that it's not currently used in Toga, but we do use the thing that preceded__cast__
- direct access to the__jni__
attribute. This is a bit of a hack that allows Python to pass around the "raw" JNI object rather than wrapping it.
As discussed, __jni__
isn't actually used in Toga, and shouldn't be used in user code either, so we don't need to emulate it. __cast__
can be emulated using Chaquopy's cast
function in the same way as __null__
.
I'm intrigued what makes Rubicon marginally faster in the "2+ run" case; I'm guessing it's the space+first start tradeoff? (i.e., Chaquopy doesn't unpack everything, which means it's faster on first run and uses less space, but every other run still needs to do in-memory unpacking which isn't as fast as direct access?)
That's correct. It's been a couple of years since I looked at this area in Chaquopy, so it's possible there are some low-hanging fruit, but we agreed that the current numbers are acceptable and it's not worth spending time on at the moment.
I tested the Rubicon API emulation using the following app (updated 2022-09-26):
import math
import sys
import toga
from toga.colors import WHITE, rgb
from toga.fonts import SANS_SERIF
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
class Hello(toga.App):
def startup(self):
self.progress = toga.ProgressBar(value=60, max=100)
self.canvas = toga.Canvas(style=Pack(flex=1))
main_box = toga.Box(style=Pack(direction=COLUMN), children=[
toga.Label(sys.version),
# Uses __null__.
self.progress,
# Uses ArrayAdapter, which uses _alternates.
toga.Selection(items=["alpha", "bravo", "charlie"]),
toga.TextInput(value="TextInput"),
toga.Button("Button"),
toga.Slider(range=(0, 100), value=40, on_change=self.on_change_slider),
self.canvas,
])
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.draw_tiberius()
self.main_window.show()
def on_change_slider(self, slider):
self.progress.value = int(slider.value)
# Copy draw_tiberius and its subroutines from https://toga.readthedocs.io/en/latest/tutorial/tutorial-4.html,
# except for draw_text, which crashes on Android because Android's Canvas.measure_text returns None.
The Canvas implementation is incomplete, but it looks exactly the same as it did with Rubicon.
After the most recent commit, we're able to pass the Rubicon test suite with the following modifications:
The main differences are:
null
as None
rather than a typed null.com/example/Thing
).jarray
.I don't think any of these are likely to cause problems in user code. And if they do, they'll be easy to fix.
There should be no more significant changes needed to this template: the remaining work to release this is in other components:
Briefcase:
Chaquopy: see milestone 13.0. Some notes on specific issues:
class
statement.class MainWindow(Window)
with MainWindow = Window
, and updating Toga's test_implementation
to accept that.The one thing that isn't on this list is porting Toga to use "native" Chaquopy, rather than Rubicon-Java. If I'm understanding correctly, chaquo/chaquopy#10 is the only true pre-requisite of that port; chaquo/chaquopy#660 and beeware/briefcase#813 are effective pre-requisites, because developing a Toga update without them will be a PITA.
The related question is release strategy for this change. Do we:
(2) would let us defer at least chaquo/chaquopy#10; there's no particular deadline for landing this work, and it might be desirable to get some user testing before we switch. However, if experience is any guide, I'm not sure we'll actually get that much user testing until we actually make the change official.
Any thoughts on release strategy?
I think for safety we should have a transitional release where Toga continues to use the Rubicon shim and doesn't depend on any Chaquopy-only features. That way, if Chaquopy causes some unforeseen problem, users will have the option of going back to the Rubicon template without losing all the other improvements in the new Toga version.
Even after Toga switches over to using Chaquopy directly, we'll stil have to keep the shim for some time to support any existing user code that uses Android APIs. But we can give the shim module a DeprecationWarning to encourage those users to switch to Chaquopy as well.
Speaking of DeprecationWarning, I think we should enable that in the Android log for the same reason as we should in briefcase dev
(https://github.com/chaquo/chaquopy/issues/664).
Removed most of the Chaquopy issues list above, as everything is now listed under the Chaquopy milestone 13.0.
As discussed, in order to get the Chaquopy release out this week and the Briefcase release out next week, we'll postpone both https://github.com/chaquo/chaquopy/issues/10 and https://github.com/chaquo/chaquopy/issues/659.
(Original comment moved to https://github.com/beeware/briefcase/discussions/1289)
@ccinsz Attaching a comment to a year-old pull request isn't an effective way to get assistance with a problem.
If you'd like help diagnosing a problem start a discussion in the Briefcase discussion forum.
If you believe you've found a bug, open a new issue, either here (if you believe it's a bug with the Android template), or on the Briefcase repository if you believe it's a more generic problem with Briefcase.
Chaquopy work in progress.
Initial proof of concept app (with
matplotlib
added torequires
in pyproject.toml):[2023-01-05: Switched from NamedTemporaryFile to BytesIO so it works on Windows: this requires Toga 0.3.0dev39 or later.]
PR Checklist: