Skip to content

Commit 583e1eb

Browse files
committed
Add Chaquopy to Android
1 parent 17871d7 commit 583e1eb

File tree

4 files changed

+123
-3
lines changed

4 files changed

+123
-3
lines changed

apps/android_pythonnative/app/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id 'com.android.application'
33
id 'org.jetbrains.kotlin.android'
4+
id 'com.chaquo.python'
45
}
56

67
android {
@@ -18,6 +19,15 @@ android {
1819
vectorDrawables {
1920
useSupportLibrary true
2021
}
22+
23+
ndk {
24+
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
25+
}
26+
python {
27+
pip {
28+
install "matplotlib"
29+
}
30+
}
2131
}
2232

2333
buildTypes {

apps/android_pythonnative/app/src/main/java/com/pythonnative/pythonnative/MainActivity.kt

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,52 @@ import androidx.compose.ui.Modifier
1212
import androidx.compose.ui.tooling.preview.Preview
1313
import com.pythonnative.pythonnative.ui.theme.PythonnativeTheme
1414

15+
import android.graphics.BitmapFactory
16+
import android.widget.Toast
17+
import androidx.compose.foundation.Image
18+
import androidx.compose.foundation.layout.Arrangement
19+
import androidx.compose.foundation.layout.Column
20+
import androidx.compose.foundation.layout.fillMaxWidth
21+
import androidx.compose.foundation.layout.padding
22+
import androidx.compose.foundation.layout.wrapContentHeight
23+
import androidx.compose.material3.ExperimentalMaterial3Api
24+
import androidx.compose.material3.OutlinedTextField
25+
import androidx.compose.runtime.*
26+
import androidx.compose.ui.graphics.asImageBitmap
27+
import androidx.compose.ui.layout.ContentScale
28+
import androidx.compose.ui.platform.LocalContext
29+
import androidx.compose.ui.unit.dp
30+
import com.chaquo.python.PyException
31+
import com.chaquo.python.Python
32+
import com.chaquo.python.android.AndroidPlatform
33+
import kotlinx.coroutines.Dispatchers
34+
import kotlinx.coroutines.launch
35+
import kotlinx.coroutines.withContext
36+
import androidx.compose.material3.Button
37+
import androidx.compose.material3.Text
38+
import androidx.compose.ui.ExperimentalComposeUiApi
39+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
40+
41+
42+
43+
1544
class MainActivity : ComponentActivity() {
1645
override fun onCreate(savedInstanceState: Bundle?) {
1746
super.onCreate(savedInstanceState)
1847
setContent {
1948
PythonnativeTheme {
20-
// A surface container using the 'background' color from the theme
2149
Surface(
2250
modifier = Modifier.fillMaxSize(),
2351
color = MaterialTheme.colorScheme.background
2452
) {
25-
Greeting("Android")
53+
MainScreen()
2654
}
2755
}
2856
}
2957
}
3058
}
3159

60+
3261
@Composable
3362
fun Greeting(name: String, modifier: Modifier = Modifier) {
3463
Text(
@@ -43,4 +72,70 @@ fun GreetingPreview() {
4372
PythonnativeTheme {
4473
Greeting("Android")
4574
}
46-
}
75+
}
76+
77+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
78+
@Composable
79+
fun MainScreen() {
80+
val context = LocalContext.current
81+
var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
82+
val coroutineScope = rememberCoroutineScope()
83+
84+
// Initialize Chaquopy
85+
if (!Python.isStarted()) {
86+
Python.start(AndroidPlatform(context))
87+
}
88+
val py = Python.getInstance()
89+
val plotModule = py.getModule("plot")
90+
91+
// Variables to keep the user's input
92+
var xInput by remember { mutableStateOf("") }
93+
var yInput by remember { mutableStateOf("") }
94+
95+
val keyboardController = LocalSoftwareKeyboardController.current
96+
97+
Column(
98+
modifier = Modifier.padding(16.dp),
99+
verticalArrangement = Arrangement.spacedBy(16.dp)
100+
) {
101+
OutlinedTextField(
102+
value = xInput,
103+
onValueChange = { xInput = it },
104+
label = { Text("Input X") }
105+
)
106+
OutlinedTextField(
107+
value = yInput,
108+
onValueChange = { yInput = it },
109+
label = { Text("Input Y") }
110+
)
111+
Button(onClick = {
112+
coroutineScope.launch {
113+
try {
114+
val bytes = plotModule.callAttr(
115+
"plot",
116+
xInput,
117+
yInput
118+
).toJava(ByteArray::class.java)
119+
withContext(Dispatchers.IO) {
120+
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
121+
}
122+
} catch (e: PyException) {
123+
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
124+
}
125+
keyboardController?.hide()
126+
}
127+
}) {
128+
Text(text = "Generate Plot")
129+
}
130+
bitmap?.let {
131+
Image(
132+
bitmap = it.asImageBitmap(),
133+
contentDescription = "Generated plot",
134+
modifier = Modifier
135+
.fillMaxWidth()
136+
.wrapContentHeight(),
137+
contentScale = ContentScale.FillWidth
138+
)
139+
}
140+
}
141+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import io
2+
import matplotlib.pyplot as plt
3+
4+
5+
def plot(x, y):
6+
xa = [float(word) for word in x.split()]
7+
ya = [float(word) for word in y.split()]
8+
9+
fig, ax = plt.subplots()
10+
ax.plot(xa, ya)
11+
12+
f = io.BytesIO()
13+
plt.savefig(f, format="png")
14+
return f.getvalue()

apps/android_pythonnative/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ plugins {
33
id 'com.android.application' version '8.0.2' apply false
44
id 'com.android.library' version '8.0.2' apply false
55
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
6+
id 'com.chaquo.python' version '14.0.2' apply false
67
}

0 commit comments

Comments
 (0)