-
-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chaquopy #52
Chaquopy #52
Conversation
versionCode {{ cookiecutter.version_code }} | ||
versionName "{{ cookiecutter.version }}" | ||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
|
||
minSdkVersion 16 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chaquopy doesn't have the same JNI issues as Rubicon (beeware/briefcase#538, beeware/rubicon-java#74), so the minimum Android version can be significantly reduced.
externalNativeBuild { | ||
cmake { | ||
cppFlags "-std=c++14" | ||
} | ||
} | ||
ndk { | ||
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I'll set this to the same ABIs as the current support package. Later we should make it a template variable which is configurable in pyproject.toml, so the user can remove ABIs to reduce the APK size, or add "x86" if they still use that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, rather than making it a template variable, requiring a rerun of briefcase create
and a full rebuild, maybe we can make build.gradle read it from a .properties file which is updated by briefcase update
.
Or even make briefcase update
update build.gradle directly: abiFilters
should only appear once, so it wouldn't be complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to making it templated in some fashion.
Writing to a .properties
file sounds like a viable approach for this specific problem in the Android case. The general problem of "update the app template after the app has been generated" is logged as beeware/briefcase#472, if we can fix the general problem, that would be an added bonus.
def JavaInterface(name): | ||
# FIXME won't work if a class implements multiple interfaces: does Toga need that? | ||
return java.dynamic_proxy(java.jclass(name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, Rubicon doesn't actually support implementing more than one Java interface in the same class. So this should be fine for the initial release, and there's no immediate need for Chaquopy to allow implementing interfaces without dynamic_proxy
syntax.
However, Chaquopy does require the dynamic_proxy
expression to be the first argument of the class
statement. It doesn't look like this would be a problem for Toga, but it might be a problem for user code which accesses Android APIs. I don't think this requirement is really necessary, so it shouldn't be hard to fix Chaquopy to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct that Rubicon doesn't currently support multiple interfaces on a class; we're only ever implementing a callback interface for a widget, so complex class hierarchies haven't ever been needed.
I wouldn't be overly concerned about code in the wild interacting with Android APIs directly; Rubicon-Java is sufficiently in the early-stage (and undocumented) that I'm comfortable with relatively major backwards incompatibilities. That said, if the problem is relatively easy to avoid, then that's obviously preferable.
I've found one more Rubicon API we'll need to emulate: @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:
Can you think of anything else? EDIT: I've found another one, |
Performance comparisons:
|
Review of open rubicon-java issues:
|
The current state of
I would definitely expect to see 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 One thing that would be interesting to see prototyped is whether we can remove |
Chaquopy accepts
As discussed,
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: Test suite patchdiff --git a/org/beeware/rubicon/test/Example.java b/org/beeware/rubicon/test/Example.java
index 8e490df..2b54f6e 100644
--- a/org/beeware/rubicon/test/Example.java
+++ b/org/beeware/rubicon/test/Example.java
@@ -94,8 +94,8 @@ public class Example extends BaseExample {
int_field = value;
}
- protected Example(String value) {
- // A protected constructor - it exists, but can't be accessed by Python.
+ private Example(String value) {
+ // A private constructor - it exists, but can't be accessed by Python.
super(999);
}
@@ -277,10 +277,10 @@ public class Example extends BaseExample {
}
/* Interface visiblity */
- protected void invisible_method(int value) {}
- protected static void static_invisible_method(int value) {}
- protected int invisible_field;
- protected static int static_invisible_field;
+ private void invisible_method(int value) {}
+ private static void static_invisible_method(int value) {}
+ private int invisible_field;
+ private static int static_invisible_field;
/* Callback handling */
public void set_callback(ICallback cb) {
diff --git a/tests/test_rubicon.py b/tests/test_rubicon.py
index 76bdd9f..57a6b78 100644
--- a/tests/test_rubicon.py
+++ b/tests/test_rubicon.py
@@ -1,8 +1,9 @@
import math
import sys
-from unittest import TestCase
+from unittest import TestCase, skip
-from rubicon.java import JavaClass, JavaInterface, JavaNull, jdouble, jfloat, jstring, jlong, jshort, jint
+from rubicon.java import (JavaClass, JavaInterface, JavaNull, jdouble, jfloat, jstring, jlong, jshort, jint,
+ jarray, jbyte, jboolean)
class JNITest(TestCase):
@@ -16,10 +17,8 @@ class JNITest(TestCase):
stack.push("Hello")
stack.push("World")
- # The stack methods are protyped to return java/lang/Object,
- # so we need to call toString() to get the actual content...
- self.assertEqual(stack.pop().toString(), "World")
- self.assertEqual(stack.pop().toString(), "Hello")
+ self.assertEqual(stack.pop(), "World")
+ self.assertEqual(stack.pop(), "Hello")
# with self.assertRaises(Exception):
# stack.pop()
@@ -31,7 +30,7 @@ class JNITest(TestCase):
class ImpossibleStackSubclass(Stack):
pass
- self.assertRaises(NotImplementedError, make_subclass)
+ self.assertRaises(TypeError, make_subclass)
def test_field(self):
"A field on an instance can be accessed and mutated"
@@ -80,6 +79,9 @@ class JNITest(TestCase):
def test_static_method(self):
"A static method on a class can be invoked."
with ExampleClassWithCleanup() as Example:
+ Example.static_base_int_field = 1137
+ Example.static_int_field = 1142
+
self.assertEqual(Example.get_static_base_int_field(), 1137)
self.assertEqual(Example.get_static_int_field(), 1142)
@@ -141,7 +143,7 @@ class JNITest(TestCase):
with self.assertRaises(AttributeError):
Example.static_method_doesnt_exist()
- def test_protected_field(self):
+ def test_private_field(self):
"An attribute error is raised if you invoke a non-public field."
Example = JavaClass('org/beeware/rubicon/test/Example')
@@ -155,7 +157,7 @@ class JNITest(TestCase):
with self.assertRaises(AttributeError):
obj1.invisible_field
- def test_protected_method(self):
+ def test_private_method(self):
"An attribute error is raised if you invoke a non-public method."
Example = JavaClass('org/beeware/rubicon/test/Example')
@@ -169,7 +171,7 @@ class JNITest(TestCase):
with self.assertRaises(AttributeError):
obj1.invisible_method()
- def test_protected_static_field(self):
+ def test_private_static_field(self):
"An attribute error is raised if you invoke a non-public static field."
Example = JavaClass('org/beeware/rubicon/test/Example')
@@ -181,7 +183,7 @@ class JNITest(TestCase):
with self.assertRaises(AttributeError):
Example.static_invisible_field
- def test_protected_static_method(self):
+ def test_private_static_method(self):
"An attribute error is raised if you invoke a non-public static method."
Example = JavaClass('org/beeware/rubicon/test/Example')
@@ -210,8 +212,8 @@ class JNITest(TestCase):
self.assertEqual(obj3.base_int_field, 3342)
self.assertEqual(obj3.int_field, 3337)
- # Protected constructors can't be invoked
- with self.assertRaises(ValueError):
+ # Private constructors can't be invoked
+ with self.assertRaises(TypeError):
Example("Hello")
def test_polymorphic_method(self):
@@ -223,7 +225,7 @@ class JNITest(TestCase):
self.assertEqual(obj1.doubler("wibble"), "wibblewibble")
# If arguments don't match available options, an error is raised
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
obj1.doubler(1.234)
def test_byte_array_arg(self):
@@ -231,16 +233,20 @@ class JNITest(TestCase):
Example = JavaClass('org/beeware/rubicon/test/Example')
obj1 = Example()
- self.assertEqual(obj1.doubler(b'abcd'), b'aabbccdd')
+ self.assertEqual(obj1.doubler(jarray(jbyte)(b'abcd')), b'aabbccdd')
def test_int_array_arg(self):
"Arrays of int can be used as arguments"
Example = JavaClass('org/beeware/rubicon/test/Example')
obj1 = Example()
- self.assertEqual(obj1.doubler([1, 2]), [1, 1, 2, 2])
- self.assertEqual(obj1.doubler([jlong(1), jlong(2)]), [1, 1, 2, 2])
- self.assertEqual(obj1.doubler([jshort(1), jshort(2)]), [1, 1, 2, 2])
- self.assertEqual(obj1.doubler([jint(1), jint(2)]), [1, 1, 2, 2])
+ self.assertEqual(obj1.doubler(jarray(jint)([1, 2])),
+ [1, 1, 2, 2])
+ self.assertEqual(obj1.doubler(jarray(jlong)([jlong(1), jlong(2)])),
+ [1, 1, 2, 2])
+ self.assertEqual(obj1.doubler(jarray(jshort)([jshort(1), jshort(2)])),
+ [1, 1, 2, 2])
+ self.assertEqual(obj1.doubler(jarray(jint)([jint(1), jint(2)])),
+ [1, 1, 2, 2])
def assertAlmostEqualList(self, actual, expected):
self.assertEqual(len(expected), len(actual), "Lists are different length")
@@ -252,21 +258,26 @@ class JNITest(TestCase):
Example = JavaClass('org/beeware/rubicon/test/Example')
obj1 = Example()
- self.assertAlmostEqualList(obj1.doubler([1.1, 2.2]), [1.1, 1.1, 2.2, 2.2])
- self.assertAlmostEqualList(obj1.doubler([jfloat(1.1), jfloat(2.2)]), [1.1, 1.1, 2.2, 2.2])
- self.assertAlmostEqualList(obj1.doubler([jdouble(1.1), jdouble(2.2)]), [1.1, 1.1, 2.2, 2.2])
+ self.assertAlmostEqualList(obj1.doubler(jarray(jfloat)([1.1, 2.2])),
+ [1.1, 1.1, 2.2, 2.2])
+ self.assertAlmostEqualList(obj1.doubler(jarray(jfloat)([jfloat(1.1), jfloat(2.2)])),
+ [1.1, 1.1, 2.2, 2.2])
+ self.assertAlmostEqualList(obj1.doubler(jarray(jdouble)([jdouble(1.1), jdouble(2.2)])),
+ [1.1, 1.1, 2.2, 2.2])
def test_bool_array_arg(self):
"Arrays of bool can be used as arguments"
Example = JavaClass('org/beeware/rubicon/test/Example')
obj1 = Example()
- self.assertEqual(obj1.doubler([True, False]), [True, True, False, False])
+ self.assertEqual(obj1.doubler(jarray(jboolean)([True, False])),
+ [True, True, False, False])
def test_string_array_arg(self):
"Arrays of string can be used as arguments"
Example = JavaClass('org/beeware/rubicon/test/Example')
obj1 = Example()
- self.assertEqual(obj1.doubler(["one", "two"]), ["one", "one", "two", "two"])
+ self.assertEqual(obj1.doubler(jarray(jstring)(["one", "two"])),
+ ["one", "one", "two", "two"])
def test_object_array_arg(self):
"Arrays of object can be used as arguments"
@@ -278,7 +289,7 @@ class JNITest(TestCase):
thing2 = Thing('This is two', 2)
self.assertEqual(
- [str(obj) for obj in obj1.doubler([thing1, thing2])],
+ [str(obj) for obj in obj1.doubler(jarray(Thing)([thing1, thing2]))],
[str(obj) for obj in [thing1, thing1, thing2, thing2]]
)
@@ -344,7 +355,7 @@ class JNITest(TestCase):
)
# If NULL arguments don't match available options, an error is raised
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
obj1.combiner(3, "Pork", JavaNull(str), handler, [1, 2]),
def test_polymorphic_method_null(self):
@@ -355,7 +366,7 @@ class JNITest(TestCase):
self.assertEqual(obj1.doubler(JavaNull(str)), "Can't double NULL strings")
def test_null_return(self):
- "Returned NULL objects are typed"
+ "Returned NULL objects are not typed"
Example = JavaClass('org/beeware/rubicon/test/Example')
obj = Example()
@@ -363,12 +374,10 @@ class JNITest(TestCase):
obj.set_thing(Thing.__null__)
returned = obj.get_thing()
- # Typed null objects are always equal to equivalent typed nulls
- self.assertEqual(returned, Thing.__null__)
- # All Typed nulls are equivalent
- self.assertEqual(returned, Example.__null__)
+ self.assertIsNone(returned)
+
# Null is always false
- self.assertFalse(returned)
+ self.assertFalse(Thing.__null__)
def test_java_null_construction(self):
"Java NULLs can be constructed"
@@ -376,7 +385,8 @@ class JNITest(TestCase):
obj1 = Example()
# Java nulls can be constructed explicitly
- self.assertEqual(JavaNull(b"Lcom/example/Thing;")._signature, b"Lcom/example/Thing;")
+ self.assertEqual(JavaNull(b"Lorg/beeware/rubicon/test/Thing;")._signature,
+ b"Lorg/beeware/rubicon/test/Thing;")
# Java nulls can be constructed from a JavaClass
self.assertEqual(JavaNull(Example)._signature, b"Lorg/beeware/rubicon/test/Example;")
@@ -384,9 +394,13 @@ class JNITest(TestCase):
# Java nulls can be constructed from an instance
self.assertEqual(JavaNull(obj1)._signature, b"Lorg/beeware/rubicon/test/Example;")
- # A Java Null can be constructed for Python or JNI primitives
- self.assertEqual(JavaNull(int)._signature, b"I")
- self.assertEqual(JavaNull(jlong)._signature, b"J")
+ # A Java Null cannot be constructed for Python or JNI primitives
+ with self.assertRaises(TypeError):
+ JavaNull(int)
+ with self.assertRaises(TypeError):
+ JavaNull(jlong)
+
+ # A Java Null can be constructed for strings
self.assertEqual(JavaNull(str)._signature, b"Ljava/lang/String;")
self.assertEqual(JavaNull(jstring)._signature, b"Ljava/lang/String;")
@@ -406,7 +420,8 @@ class JNITest(TestCase):
self.assertEqual(JavaNull([Example])._signature, b"[Lorg/beeware/rubicon/test/Example;")
# A Java Null for an array of explicit JNI references
- self.assertEqual(JavaNull([b'Lcom/example/Thing;'])._signature, b"[Lcom/example/Thing;")
+ self.assertEqual(JavaNull([b'Lorg/beeware/rubicon/test/Thing;'])._signature,
+ b"[Lorg/beeware/rubicon/test/Thing;")
# Arrays are defined with a type, not a literal
with self.assertRaises(ValueError):
@@ -427,13 +442,13 @@ class JNITest(TestCase):
def test_null_repr(self):
"Null objects can be output to console"
# Output of a null makes sense
- self.assertEqual(repr(JavaNull(b'Lcom/example/Thing;')), "<Java NULL (Lcom/example/Thing;)>")
- self.assertEqual(repr(JavaNull(str)), "<Java NULL (Ljava/lang/String;)>")
- self.assertEqual(repr(JavaNull(int)), "<Java NULL (I)>")
+ self.assertEqual(repr(JavaNull(b'Lorg/beeware/rubicon/test/Thing;')),
+ "cast('Lorg/beeware/rubicon/test/Thing;', None)")
+ self.assertEqual(repr(JavaNull(str)), "cast('Ljava/lang/String;', None)")
- self.assertEqual(str(JavaNull(b'Lcom/example/Thing;')), "<NULL>")
- self.assertEqual(str(JavaNull(str)), "<NULL>")
- self.assertEqual(str(JavaNull(int)), "<NULL>")
+ self.assertEqual(str(JavaNull(b'Lorg/beeware/rubicon/test/Thing;')),
+ "cast('Lorg/beeware/rubicon/test/Thing;', None)")
+ self.assertEqual(str(JavaNull(str)), "cast('Ljava/lang/String;', None)")
def test_polymorphic_static_method(self):
"Check that the right static method is activated based on arguments used"
@@ -443,7 +458,7 @@ class JNITest(TestCase):
self.assertEqual(Example.tripler("wibble"), "wibblewibblewibble")
# If arguments don't match available options, an error is raised
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.tripler(1.234)
def test_type_cast(self):
@@ -457,14 +472,15 @@ class JNITest(TestCase):
obj1.set_thing(thing)
# Retrieve a generic reference to the object (java.lang.Object)
- obj = obj1.get_generic_thing()
+ Object = JavaClass('java/lang/Object')
+ obj = Object.__cast__(obj1.get_generic_thing())
ICallback_null = JavaNull(b'Lorg/beeware/rubicon/test/ICallback;')
# Attempting to use this generic object *as* a Thing will fail.
with self.assertRaises(AttributeError):
obj.currentCount()
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
obj1.combiner(3, "Ham", obj, ICallback_null, JavaNull([int]))
# ...but if we cast it to the type we know it is
@@ -509,27 +525,26 @@ class JNITest(TestCase):
def test_heterogenous_list(self):
"""A list of mixed types raise an exception when trying to find the right Java method."""
Example = JavaClass("org/beeware/rubicon/test/Example")
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.sum_all_ints(["two", 3])
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.sum_all_ints([1, "two"])
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.sum_all_floats([1.0, "two"])
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.sum_all_doubles([1.0, "two"])
def test_list_that_cannot_be_turned_into_java_primitive_array(self):
"""A list that can't turn into a Java primitive array raises an exception when trying to find the right
Java method."""
Example = JavaClass("org/beeware/rubicon/test/Example")
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
Example.sum_all_ints([object()])
def test_empty_list(self):
- """An empty list results in an inability to find the right Java method."""
+ """An empty list is still able to find the right Java method."""
Example = JavaClass("org/beeware/rubicon/test/Example")
- with self.assertRaises(ValueError):
- Example.sum_all_ints([])
+ self.assertEqual(Example.sum_all_ints([]), 0)
def test_pass_double_array(self):
"""A list of Python floats can be passed as a Java double array."""
@@ -554,25 +569,22 @@ class JNITest(TestCase):
self.assertEqual(0, Example.xor_all_bytes(b'xx'))
def test_static_access_non_static(self):
- "An instance field/method cannot be accessed from the static context"
+ "A static field/method can be accessed from an instance context"
Example = JavaClass('org/beeware/rubicon/test/Example')
obj = Example()
- with self.assertRaises(AttributeError):
- obj.static_int_field
-
- with self.assertRaises(AttributeError):
- obj.get_static_int_field()
+ self.assertEqual(obj.static_int_field, 11)
+ self.assertEqual(obj.get_static_int_field(), 11)
def test_non_static_access_static(self):
- "A static field/method cannot be accessed from an instance context"
+ "An instance field/method cannot be accessed from a static context"
Example = JavaClass('org/beeware/rubicon/test/Example')
with self.assertRaises(AttributeError):
Example.int_field
- with self.assertRaises(AttributeError):
+ with self.assertRaises(TypeError):
Example.get_int_field()
def test_string_argument(self):
@@ -717,6 +729,7 @@ class JNITest(TestCase):
self.assertFalse(TruthInverter().invert(true_maker))
self.assertTrue(TruthInverter().invert(false_maker))
+ @skip("_alternates is not in the public API")
def test_alternatives(self):
"A class is aware of it's type hierarchy"
Example = JavaClass('org/beeware/rubicon/test/Example') The main differences are:
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:
|
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 |
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 chaquo/chaquopy#10 and chaquo/chaquopy#659. |
# Conflicts: # {{ cookiecutter.safe_formal_name }}/briefcase.toml
* Update Chaquopy to version 13.0.0, and add Python version setting * Update targetSdkVersion to 33 * Revert to default gradle.properties file
# Automatically convert third-party libraries to use AndroidX | ||
android.enableJetifier=true | ||
# Kotlin code style for this project: "official" or "obsolete": | ||
kotlin.code.style=official |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chaquopy doesn't require any of the previous changes to this file, so I've reverted it to the default version generated by Android Studio 4.2, which corresponds to the Android Gradle plugin version in build.gradle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! 👍
(Original comment moved to beeware/briefcase#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: